@trackunit/react-form-components 1.8.121 → 1.8.123
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/index.cjs.js +873 -576
- package/index.esm.js +863 -572
- package/package.json +9 -8
- package/src/components/BaseInput/BaseInput.d.ts +1 -5
- package/src/components/BaseSelect/BaseSelect.d.ts +2 -5
- package/src/components/BaseSelect/BaseSelect.variants.d.ts +26 -16
- package/src/components/BaseSelect/CreatableSelect.d.ts +15 -10
- package/src/components/BaseSelect/custom-components/CounterTag.d.ts +15 -0
- package/src/components/BaseSelect/custom-components/MultiValue.d.ts +18 -0
- package/src/components/BaseSelect/{SelectMenuItem → custom-components/SelectMenuItem}/SelectMenuItem.d.ts +3 -3
- package/src/components/BaseSelect/index.d.ts +2 -1
- package/src/components/BaseSelect/useCreatableSelect.d.ts +10 -0
- package/src/components/BaseSelect/useCustomComponents.d.ts +26 -23
- package/src/components/BaseSelect/useMultiMeasure.d.ts +27 -0
- package/src/components/BaseSelect/useMultiValueOverflow.d.ts +21 -0
- package/src/components/BaseSelect/useSelect.d.ts +22 -39
- package/src/components/DropZone/DropZone.d.ts +1 -1
- package/src/components/FormGroup/FormGroup.d.ts +2 -2
- package/src/components/MultiSelectField/FormFieldSelectAdapterMulti.d.ts +21 -18
- package/src/components/MultiSelectField/MultiSelectField.d.ts +9 -4
- package/src/components/PhoneField/PhoneBaseInput/PhoneBaseInput.d.ts +2 -2
- package/src/components/SelectField/CreatableSelectField.d.ts +5 -3
- package/src/components/SelectField/FormFieldSelectAdapter.d.ts +14 -10
- package/src/components/SelectField/SelectField.d.ts +6 -9
- package/src/components/UrlField/UrlBaseInput/UrlBaseInput.d.ts +2 -1
- package/src/components/storybook-utils/sharedArgTypes.d.ts +0 -54
- package/src/translation.d.ts +2 -2
- package/src/types.d.ts +1 -1
- package/src/components/BaseSelect/TagWithWidth.d.ts +0 -16
- package/src/components/BaseSelect/TagsContainer.d.ts +0 -51
- package/src/components/BaseSelect/useCustomStyles.d.ts +0 -20
package/index.cjs.js
CHANGED
|
@@ -12,10 +12,11 @@ var usehooksTs = require('usehooks-ts');
|
|
|
12
12
|
var parsePhoneNumberFromString = require('libphonenumber-js');
|
|
13
13
|
var ReactSelect = require('react-select');
|
|
14
14
|
var ReactAsyncSelect = require('react-select/async');
|
|
15
|
+
var esToolkit = require('es-toolkit');
|
|
16
|
+
var tailwindMerge = require('tailwind-merge');
|
|
15
17
|
var ReactAsyncCreatableSelect = require('react-select/async-creatable');
|
|
16
18
|
var ReactCreatableSelect = require('react-select/creatable');
|
|
17
19
|
var sharedUtils = require('@trackunit/shared-utils');
|
|
18
|
-
var tailwindMerge = require('tailwind-merge');
|
|
19
20
|
var reactHookForm = require('react-hook-form');
|
|
20
21
|
var zod = require('zod');
|
|
21
22
|
|
|
@@ -49,6 +50,8 @@ var defaultTranslations = {
|
|
|
49
50
|
"schedule.label.allDay": "All Day",
|
|
50
51
|
"schedule.label.day": "Day",
|
|
51
52
|
"search.placeholder": "Search",
|
|
53
|
+
"select.loadingMessage": "Loading...",
|
|
54
|
+
"select.noOptionsMessage": "No options found",
|
|
52
55
|
"urlField.error.INVALID_URL": "Please enter a valid URL",
|
|
53
56
|
"urlField.error.REQUIRED": "The URL is required"
|
|
54
57
|
};
|
|
@@ -434,10 +437,8 @@ const SuffixRenderer = ({ suffix, isInvalid, isWarning, "data-testid": dataTestI
|
|
|
434
437
|
* For specific input types make sure to use the corresponding input component.
|
|
435
438
|
* This is a base used by our other input components such as TextBaseInput, NumberBaseInput, PasswordBaseInput, etc.
|
|
436
439
|
*/
|
|
437
|
-
const BaseInput = ({ className, isInvalid = false, "data-testid": dataTestId, prefix, suffix, addonBefore, addonAfter, actions, fieldSize = "medium",
|
|
440
|
+
const BaseInput = ({ className, isInvalid = false, "data-testid": dataTestId, prefix, suffix, addonBefore, addonAfter, actions, fieldSize = "medium", inputClassName, placeholder, isWarning = false, type, genericAction, style, ref, required = false, readOnly = false, disabled = false, ...rest }) => {
|
|
438
441
|
// Derive final flags
|
|
439
|
-
const renderAsDisabled = Boolean(rest.disabled);
|
|
440
|
-
const renderAsReadonly = Boolean(rest.readOnly);
|
|
441
442
|
const beforeContainerRef = react.useRef(null);
|
|
442
443
|
const afterContainerRef = react.useRef(null);
|
|
443
444
|
react.useEffect(() => {
|
|
@@ -467,19 +468,18 @@ const BaseInput = ({ className, isInvalid = false, "data-testid": dataTestId, pr
|
|
|
467
468
|
const innerRef = react.useRef(null);
|
|
468
469
|
react.useImperativeHandle(ref, () => innerRef.current, []);
|
|
469
470
|
return (jsxRuntime.jsxs("div", { className: cvaInput$1({
|
|
470
|
-
disabled:
|
|
471
|
-
readOnly: renderAsReadonly,
|
|
471
|
+
disabled: Boolean(disabled),
|
|
472
472
|
invalid: isInvalid,
|
|
473
473
|
isWarning,
|
|
474
474
|
size: fieldSize,
|
|
475
475
|
className,
|
|
476
|
-
}), "data-testid": dataTestId ? `${dataTestId}-container` : undefined, style: style, children: [jsxRuntime.jsxs("div", { className: cvaAccessoriesContainer({ className: cvaInputItemPlacementManager({ position: "before" }) }), "data-testid": dataTestId ? `${dataTestId}-before-container` : undefined, ref: beforeContainerRef, children: [jsxRuntime.jsx(AddonRenderer, { addon: addonBefore, "data-testid": dataTestId, fieldSize: fieldSize, position: "before" }), jsxRuntime.jsx(PrefixRenderer, { "data-testid": dataTestId, disabled:
|
|
476
|
+
}), "data-testid": dataTestId ? `${dataTestId}-container` : undefined, style: style, children: [jsxRuntime.jsxs("div", { className: cvaAccessoriesContainer({ className: cvaInputItemPlacementManager({ position: "before" }) }), "data-testid": dataTestId ? `${dataTestId}-before-container` : undefined, ref: beforeContainerRef, children: [jsxRuntime.jsx(AddonRenderer, { addon: addonBefore, "data-testid": dataTestId, fieldSize: fieldSize, position: "before" }), jsxRuntime.jsx(PrefixRenderer, { "data-testid": dataTestId, disabled: Boolean(disabled), prefix: prefix, type: type })] }), jsxRuntime.jsx("input", { "aria-required": required, className: cvaInputElement({
|
|
477
477
|
size: fieldSize,
|
|
478
478
|
className: cvaInputItemPlacementManager({ position: "span", className: inputClassName }),
|
|
479
|
-
}), "data-testid": dataTestId, placeholder:
|
|
479
|
+
}), "data-testid": dataTestId, placeholder: Boolean(disabled) ? undefined : placeholder, ref: innerRef, required: required, style: {
|
|
480
480
|
paddingLeft: `calc(var(--before-width, 0px) + ${uiDesignTokens.themeSpacing[2]})`,
|
|
481
481
|
paddingRight: `calc(var(--after-width, 0px) + ${uiDesignTokens.themeSpacing[2]})`,
|
|
482
|
-
}, type: type, ...rest, disabled:
|
|
482
|
+
}, type: type, ...rest, disabled: Boolean(disabled), readOnly: Boolean(readOnly) }), jsxRuntime.jsxs("div", { className: cvaAccessoriesContainer({ className: cvaInputItemPlacementManager({ position: "after" }) }), "data-testid": dataTestId ? `${dataTestId}-after-container` : undefined, ref: afterContainerRef, children: [jsxRuntime.jsx(LockReasonRenderer, { "data-testid": dataTestId + "-disabled", lockReason: disabled }), jsxRuntime.jsx(LockReasonRenderer, { "data-testid": dataTestId + "-readonly", lockReason: Boolean(readOnly) && !Boolean(disabled) ? readOnly : undefined }), jsxRuntime.jsx(GenericActionsRenderer, { fieldSize: fieldSize, genericAction: genericAction, innerRef: innerRef }), jsxRuntime.jsx(SuffixRenderer, { "data-testid": dataTestId, disabled: Boolean(disabled), isInvalid: isInvalid, isWarning: isWarning, suffix: suffix }), actions, jsxRuntime.jsx(AddonRenderer, { addon: addonAfter, "data-testid": dataTestId, fieldSize: fieldSize, position: "after" })] })] }));
|
|
483
483
|
};
|
|
484
484
|
BaseInput.displayName = "BaseInput";
|
|
485
485
|
|
|
@@ -652,7 +652,7 @@ const PhoneBaseInput = ({ "data-testid": dataTestId, isInvalid, disabled = false
|
|
|
652
652
|
onFocus?.(event);
|
|
653
653
|
fieldIsFocused.current = true;
|
|
654
654
|
}, [onFocus]);
|
|
655
|
-
return (jsxRuntime.jsx("div", { className: "grid grid-cols-1 gap-2", "data-testid": dataTestId ? `${dataTestId}-container` : null, children: jsxRuntime.jsx(BaseInput, { actions: !disableAction && innerValue && innerValue.length > 0 ? (jsxRuntime.jsx(ActionButton, { "data-testid": dataTestId ? `${dataTestId}-phoneIcon` : undefined, disabled: isInvalid, size: fieldSize
|
|
655
|
+
return (jsxRuntime.jsx("div", { className: "grid grid-cols-1 gap-2", "data-testid": dataTestId ? `${dataTestId}-container` : null, children: jsxRuntime.jsx(BaseInput, { actions: !disableAction && innerValue && innerValue.length > 0 ? (jsxRuntime.jsx(ActionButton, { "data-testid": dataTestId ? `${dataTestId}-phoneIcon` : undefined, disabled: isInvalid, size: fieldSize, type: "PHONE_NUMBER", value: value?.toString() || "" })) : null, "data-testid": dataTestId ? `${dataTestId}-phoneNumberInput` : undefined, disabled: disabled, fieldSize: fieldSize, id: "phoneInput-number", isInvalid: isInvalid, name: name, onBlur: handleBlur, onChange: handleChange, onFocus: handleFocus, prefix: (countryCode && countryCodeToFlagEmoji(countryCode)) || undefined, readOnly: readOnly, ref: ref, type: "tel", value: innerValue, ...rest }) }));
|
|
656
656
|
};
|
|
657
657
|
|
|
658
658
|
const cvaTextAreaBaseInput = cssClassVarianceUtilities.cvaMerge([
|
|
@@ -702,62 +702,54 @@ const TextAreaBaseInput = ({ id, name, value, rows, disabled, placeholder, readO
|
|
|
702
702
|
*/
|
|
703
703
|
const TextBaseInput = ({ ref, ...rest }) => jsxRuntime.jsx(BaseInput, { ref: ref, type: "text", ...rest });
|
|
704
704
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
"border-neutral-300",
|
|
711
|
-
"hover:border-neutral-400",
|
|
712
|
-
"hover:bg-neutral-50",
|
|
713
|
-
"bg-white",
|
|
714
|
-
"transition",
|
|
715
|
-
"text-sm",
|
|
716
|
-
"min-h-0",
|
|
717
|
-
], {
|
|
705
|
+
/**
|
|
706
|
+
* The container for the select component — with state styling
|
|
707
|
+
* !This is _the_ place in select styles to manage the aperance of the text and background
|
|
708
|
+
*/
|
|
709
|
+
const cvaSelectContainer = cssClassVarianceUtilities.cvaMerge([cvaInputBase(), "relative", "transition", "min-h-0"], {
|
|
718
710
|
variants: {
|
|
719
711
|
fieldSize: {
|
|
720
|
-
small:
|
|
721
|
-
medium:
|
|
722
|
-
large:
|
|
723
|
-
},
|
|
724
|
-
invalid: {
|
|
725
|
-
true: "border border-red-600 text-red-600 hover:border-red-600",
|
|
726
|
-
false: "",
|
|
712
|
+
small: cvaInputBaseSize({ size: "small" }),
|
|
713
|
+
medium: cvaInputBaseSize({ size: "medium" }),
|
|
714
|
+
large: cvaInputBaseSize({ size: "large" }),
|
|
727
715
|
},
|
|
728
716
|
disabled: {
|
|
729
|
-
true:
|
|
717
|
+
true: cvaInputBaseDisabled(),
|
|
730
718
|
false: "",
|
|
731
719
|
},
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
invalid: false,
|
|
735
|
-
disabled: false,
|
|
736
|
-
},
|
|
737
|
-
});
|
|
738
|
-
const cvaSelectControl = cssClassVarianceUtilities.cvaMerge([], {
|
|
739
|
-
variants: {
|
|
740
|
-
isDisabled: {
|
|
741
|
-
true: "!bg-neutral-100",
|
|
720
|
+
invalid: {
|
|
721
|
+
true: cvaInputBaseInvalid(),
|
|
742
722
|
false: "",
|
|
743
723
|
},
|
|
744
|
-
|
|
745
|
-
true:
|
|
724
|
+
focused: {
|
|
725
|
+
true: "outline-native",
|
|
746
726
|
false: "",
|
|
747
727
|
},
|
|
748
|
-
|
|
749
|
-
true:
|
|
728
|
+
readOnly: {
|
|
729
|
+
true: cvaInputBaseReadOnly(),
|
|
750
730
|
false: "",
|
|
751
731
|
},
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
732
|
+
defaultVariants: {
|
|
733
|
+
invalid: false,
|
|
734
|
+
disabled: false,
|
|
735
|
+
fieldSize: "medium",
|
|
736
|
+
focused: false,
|
|
737
|
+
readOnly: false,
|
|
738
|
+
},
|
|
757
739
|
},
|
|
758
740
|
});
|
|
759
|
-
const
|
|
760
|
-
|
|
741
|
+
const cvaSelectControl = cssClassVarianceUtilities.cvaMerge(["px-3", "gap-x-1", "flex", "h-full"]);
|
|
742
|
+
const cvaSelectLoadingMessage = cssClassVarianceUtilities.cvaMerge(["text-neutral-400", "text-center"]);
|
|
743
|
+
const cvaSelectNoOptionsMessage = cssClassVarianceUtilities.cvaMerge(["text-neutral-400", "text-center"]);
|
|
744
|
+
const cvaSelectDropdownIconContainer = cssClassVarianceUtilities.cvaMerge([
|
|
745
|
+
"flex",
|
|
746
|
+
"cursor-pointer",
|
|
747
|
+
"place-items-center",
|
|
748
|
+
"text-neutral-400",
|
|
749
|
+
"hover:text-neutral-500",
|
|
750
|
+
]);
|
|
751
|
+
const cvaSelectPrefixSuffix = cssClassVarianceUtilities.cvaMerge(["flex", "items-center", "text-neutral-400"]);
|
|
752
|
+
const cvaSelectClearIndicator = cssClassVarianceUtilities.cvaMerge([
|
|
761
753
|
"flex",
|
|
762
754
|
"cursor-pointer",
|
|
763
755
|
"items-center",
|
|
@@ -765,39 +757,88 @@ const cvaSelectIcon = cssClassVarianceUtilities.cvaMerge([
|
|
|
765
757
|
"text-neutral-400",
|
|
766
758
|
"hover:text-neutral-500",
|
|
767
759
|
]);
|
|
768
|
-
const
|
|
760
|
+
const cvaSelectDropdownIndicator = cssClassVarianceUtilities.cvaMerge(["transition-transform", "duration-200"], {
|
|
769
761
|
variants: {
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
762
|
+
menuIsOpen: {
|
|
763
|
+
true: "rotate-180",
|
|
764
|
+
false: "rotate-0",
|
|
773
765
|
},
|
|
774
766
|
},
|
|
775
767
|
});
|
|
776
|
-
const
|
|
777
|
-
"mr-2",
|
|
768
|
+
const cvaSelectValueContainer = cssClassVarianceUtilities.cvaMerge([
|
|
778
769
|
"flex",
|
|
779
|
-
"
|
|
770
|
+
"flex-nowrap",
|
|
780
771
|
"items-center",
|
|
781
|
-
"
|
|
782
|
-
"
|
|
783
|
-
"
|
|
784
|
-
"
|
|
772
|
+
"grow",
|
|
773
|
+
"overflow-hidden",
|
|
774
|
+
"relative",
|
|
775
|
+
"gap-x-1",
|
|
785
776
|
]);
|
|
786
|
-
|
|
777
|
+
// for the placement of items inside the value container
|
|
778
|
+
const insideValueContainerClasses = cssClassVarianceUtilities.cvaMerge(["col-start-1", "col-end-3", "row-start-1", "row-end-2"]);
|
|
779
|
+
const cvaSelectSingleValue = cssClassVarianceUtilities.cvaMerge([
|
|
780
|
+
insideValueContainerClasses(),
|
|
781
|
+
"max-w-full",
|
|
782
|
+
"overflow-hidden",
|
|
783
|
+
"text-ellipsis",
|
|
784
|
+
"whitespace-nowrap",
|
|
785
|
+
]);
|
|
786
|
+
const cvaSelectMenu = cssClassVarianceUtilities.cvaMerge([reactComponents.cvaMenu({ limitWidth: false }), "absolute", "w-full"], {
|
|
787
787
|
variants: {
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
788
|
+
placement: {
|
|
789
|
+
bottom: "top-[calc(100%+var(--spacing-1))]",
|
|
790
|
+
top: "bottom-[calc(-100%+var(--spacing-1))]",
|
|
791
791
|
},
|
|
792
792
|
},
|
|
793
|
+
defaultVariants: {
|
|
794
|
+
placement: "bottom",
|
|
795
|
+
},
|
|
793
796
|
});
|
|
794
|
-
const
|
|
797
|
+
const cvaSelectMenuList = cssClassVarianceUtilities.cvaMerge([reactComponents.cvaMenuList(), "relative", "w-full"]);
|
|
798
|
+
const cvaSelectPlaceholder = cssClassVarianceUtilities.cvaMerge(["absolute", "text-neutral-400"]);
|
|
799
|
+
const cvaSelectIndicatorsContainer = cssClassVarianceUtilities.cvaMerge(["flex", "items-center", "gap-x-1"]);
|
|
800
|
+
const cvaSelectMultiValue = cssClassVarianceUtilities.cvaMerge([], {
|
|
795
801
|
variants: {
|
|
796
|
-
|
|
802
|
+
hidden: {
|
|
803
|
+
true: "!hidden",
|
|
804
|
+
false: "",
|
|
805
|
+
},
|
|
806
|
+
invisible: {
|
|
807
|
+
true: "invisible",
|
|
808
|
+
false: "",
|
|
809
|
+
},
|
|
810
|
+
},
|
|
811
|
+
defaultVariants: {
|
|
812
|
+
hidden: false,
|
|
797
813
|
},
|
|
798
814
|
});
|
|
799
|
-
|
|
800
|
-
|
|
815
|
+
|
|
816
|
+
/**
|
|
817
|
+
* Internal component for displaying a counter badge showing hidden multi-select values.
|
|
818
|
+
* Used for measurement and display when tags overflow the container.
|
|
819
|
+
*/
|
|
820
|
+
const CounterTag = ({ fieldSize, hiddenCount, totalCount, ref, className, "data-testid": dataTestId, }) => {
|
|
821
|
+
if (hiddenCount === 0) {
|
|
822
|
+
return null;
|
|
823
|
+
}
|
|
824
|
+
return (jsxRuntime.jsx(reactComponents.Tag, { className: tailwindMerge.twMerge("inline-flex shrink-0", className), color: "neutral", "data-testid": dataTestId ?? "select-counter", ref: ref, size: fieldSize === "small" ? "small" : "medium", children: totalCount > hiddenCount ? `+${hiddenCount}` : hiddenCount }));
|
|
825
|
+
};
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* Internal component for rendering multi-select values with measurement support.
|
|
829
|
+
* Uses the measurement state to determine if the value should be displayed or hidden based on available width.
|
|
830
|
+
*/
|
|
831
|
+
const MultiValue = ({ data, children, onClose, className, disabled, fieldSize, getOptionPrefix, ref, "data-testid": dataTestId, }) => {
|
|
832
|
+
const optionPrefix = getOptionPrefix ? getOptionPrefix(data) : null;
|
|
833
|
+
const handleOnClose = (e) => {
|
|
834
|
+
if (disabled) {
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
e.stopPropagation();
|
|
838
|
+
onClose?.(e);
|
|
839
|
+
};
|
|
840
|
+
return (jsxRuntime.jsx(reactComponents.Tag, { className: tailwindMerge.twMerge(className, "shrink-0", "inline-flex"), color: disabled ? "neutral" : "white", "data-testid": dataTestId, icon: optionPrefix, onClose: disabled ? undefined : handleOnClose, ref: ref, size: fieldSize === "small" ? "small" : "medium", children: jsxRuntime.jsx("span", { className: "flex items-center gap-1", children: children }) }));
|
|
841
|
+
};
|
|
801
842
|
|
|
802
843
|
/**
|
|
803
844
|
* Shared CVA for binary control items: Checkbox, RadioItem, ToggleSwitchOption
|
|
@@ -1030,13 +1071,13 @@ Checkbox.displayName = "Checkbox";
|
|
|
1030
1071
|
* @param {SelectMenuItemProps} props - The props for the SingleSelectMenuItem
|
|
1031
1072
|
* @returns {ReactElement} SingleSelectMenuItem
|
|
1032
1073
|
*/
|
|
1033
|
-
const SingleSelectMenuItem = ({ label, icon, onClick, selected, focused, "data-testid": dataTestId,
|
|
1074
|
+
const SingleSelectMenuItem = ({ label, icon, onClick, selected = false, focused = false, disabled = false, "data-testid": dataTestId, optionLabelDescription, optionPrefix, fieldSize = "medium", }) => {
|
|
1034
1075
|
return (jsxRuntime.jsx(reactComponents.MenuItem, { "data-testid": dataTestId, disabled: disabled, fieldSize: fieldSize, focused: focused, label: label, onClick: onClick, optionLabelDescription: optionLabelDescription, optionPrefix: react.isValidElement(optionPrefix)
|
|
1035
1076
|
? react.cloneElement(optionPrefix, {
|
|
1036
1077
|
className: "mr-1 flex items-center",
|
|
1037
1078
|
size: "medium",
|
|
1038
1079
|
})
|
|
1039
|
-
: optionPrefix, prefix: icon, selected: selected, suffix: selected ? jsxRuntime.jsx(reactComponents.Icon, {
|
|
1080
|
+
: optionPrefix, prefix: icon, selected: selected, suffix: selected ? jsxRuntime.jsx(reactComponents.Icon, { color: "primary", name: "Check", size: fieldSize === "large" ? "medium" : "small" }) : undefined }));
|
|
1040
1081
|
};
|
|
1041
1082
|
/**
|
|
1042
1083
|
* A multi select menu item is a basic wrapper around Menu item designed to be used as a multi value render in Select list
|
|
@@ -1047,7 +1088,7 @@ const SingleSelectMenuItem = ({ label, icon, onClick, selected, focused, "data-t
|
|
|
1047
1088
|
const MultiSelectMenuItem = ({ label, onClick, selected, focused, "data-testid": dataTestId, disabled, optionLabelDescription, optionPrefix, fieldSize, }) => {
|
|
1048
1089
|
return (jsxRuntime.jsx(reactComponents.MenuItem, { "data-testid": dataTestId, disabled: disabled, fieldSize: fieldSize, focused: focused, label: label, onClick: e => {
|
|
1049
1090
|
e.stopPropagation();
|
|
1050
|
-
onClick
|
|
1091
|
+
onClick?.(e);
|
|
1051
1092
|
}, optionLabelDescription: optionLabelDescription, optionPrefix: react.isValidElement(optionPrefix)
|
|
1052
1093
|
? react.cloneElement(optionPrefix, {
|
|
1053
1094
|
className: "mr-1 flex items-center",
|
|
@@ -1059,542 +1100,778 @@ const MultiSelectMenuItem = ({ label, onClick, selected, focused, "data-testid":
|
|
|
1059
1100
|
};
|
|
1060
1101
|
|
|
1061
1102
|
/**
|
|
1062
|
-
*
|
|
1063
|
-
*
|
|
1103
|
+
* Custom hook to measure the geometry of multiple elements indexed by number.
|
|
1104
|
+
* Similar to useMeasure but handles multiple elements efficiently with a single ResizeObserver.
|
|
1064
1105
|
*
|
|
1065
|
-
* @param {
|
|
1066
|
-
* @returns {
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
const
|
|
1083
|
-
const
|
|
1084
|
-
const
|
|
1085
|
-
const [counterWidth, setCounterWidth] = react.useState(0);
|
|
1086
|
-
const [availableSpaceWidth, setAvailableSpaceWidth] = react.useState(0);
|
|
1087
|
-
const [childrenWidths, setChildrenWidths] = react.useState([]);
|
|
1088
|
-
const itemsCount = items.length;
|
|
1089
|
-
const dimensions = reactComponents.useResize();
|
|
1090
|
-
const { width: windowWidth } = reactComponents.useDebounce(dimensions, { delay: 100 });
|
|
1106
|
+
* @param {UseMultiMeasureOptions} options - Configuration options
|
|
1107
|
+
* @returns {UseMultiMeasureResult} An object containing `geometries` Map and `getRef` function to create refs
|
|
1108
|
+
* @example
|
|
1109
|
+
* ```tsx
|
|
1110
|
+
* const { geometries, getRef } = useMultiMeasure({
|
|
1111
|
+
* onChange: (geometries) => console.log('Geometries changed', geometries)
|
|
1112
|
+
* });
|
|
1113
|
+
* return items.map((item, index) => (
|
|
1114
|
+
* <div key={index} ref={getRef(index)}>Item {index}</div>
|
|
1115
|
+
* ));
|
|
1116
|
+
* ```
|
|
1117
|
+
*/
|
|
1118
|
+
const useMultiMeasure = ({ skip = false, onChange } = {}) => {
|
|
1119
|
+
const [geometries, setGeometries] = react.useState(new Map());
|
|
1120
|
+
const [elementCount, setElementCount] = react.useState(0); // Track element count to trigger useLayoutEffect
|
|
1121
|
+
const elementsRef = react.useRef(new Map());
|
|
1122
|
+
const observerRef = react.useRef(null);
|
|
1123
|
+
const onChangeRef = react.useRef(onChange);
|
|
1124
|
+
const refCallbacksRef = react.useRef(new Map());
|
|
1125
|
+
const updateScheduledRef = react.useRef(false);
|
|
1091
1126
|
react.useEffect(() => {
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
const
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1127
|
+
onChangeRef.current = onChange;
|
|
1128
|
+
}, [onChange]);
|
|
1129
|
+
// Update geometries for all currently tracked elements
|
|
1130
|
+
const updateAllGeometries = react.useCallback(() => {
|
|
1131
|
+
// Batch multiple rapid calls (like during unmount/remount) into a single update
|
|
1132
|
+
if (updateScheduledRef.current) {
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
updateScheduledRef.current = true;
|
|
1136
|
+
queueMicrotask(() => {
|
|
1137
|
+
updateScheduledRef.current = false;
|
|
1138
|
+
const newGeometries = new Map();
|
|
1139
|
+
elementsRef.current.forEach((element, index) => {
|
|
1140
|
+
const rect = element.getBoundingClientRect();
|
|
1141
|
+
newGeometries.set(index, {
|
|
1142
|
+
width: rect.width,
|
|
1143
|
+
height: rect.height,
|
|
1144
|
+
top: rect.top,
|
|
1145
|
+
bottom: rect.bottom,
|
|
1146
|
+
left: rect.left,
|
|
1147
|
+
right: rect.right,
|
|
1148
|
+
x: rect.x,
|
|
1149
|
+
y: rect.y,
|
|
1150
|
+
});
|
|
1151
|
+
});
|
|
1152
|
+
setGeometries(prevGeometries => {
|
|
1153
|
+
if (esToolkit.isEqual(prevGeometries, newGeometries)) {
|
|
1154
|
+
return prevGeometries;
|
|
1155
|
+
}
|
|
1156
|
+
onChangeRef.current?.(newGeometries);
|
|
1157
|
+
return newGeometries;
|
|
1158
|
+
});
|
|
1159
|
+
});
|
|
1160
|
+
}, []);
|
|
1161
|
+
// Measure elements after they're mounted (useLayoutEffect runs synchronously after DOM updates)
|
|
1162
|
+
react.useLayoutEffect(() => {
|
|
1163
|
+
if (skip || elementsRef.current.size === 0) {
|
|
1164
|
+
return;
|
|
1165
|
+
}
|
|
1166
|
+
// Initial synchronous measurement - this runs in the same render cycle, so tests don't need waitFor
|
|
1167
|
+
const newGeometries = new Map();
|
|
1168
|
+
elementsRef.current.forEach((element, index) => {
|
|
1169
|
+
const rect = element.getBoundingClientRect();
|
|
1170
|
+
newGeometries.set(index, {
|
|
1171
|
+
width: rect.width,
|
|
1172
|
+
height: rect.height,
|
|
1173
|
+
top: rect.top,
|
|
1174
|
+
bottom: rect.bottom,
|
|
1175
|
+
left: rect.left,
|
|
1176
|
+
right: rect.right,
|
|
1177
|
+
x: rect.x,
|
|
1178
|
+
y: rect.y,
|
|
1179
|
+
});
|
|
1180
|
+
});
|
|
1181
|
+
setGeometries(prevGeometries => {
|
|
1182
|
+
if (esToolkit.isEqual(prevGeometries, newGeometries)) {
|
|
1183
|
+
return prevGeometries;
|
|
1100
1184
|
}
|
|
1101
|
-
|
|
1185
|
+
onChangeRef.current?.(newGeometries);
|
|
1186
|
+
return newGeometries;
|
|
1102
1187
|
});
|
|
1103
|
-
}, [
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
.
|
|
1111
|
-
.
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1188
|
+
}, [skip, elementCount]);
|
|
1189
|
+
// Set up ResizeObserver for subsequent changes
|
|
1190
|
+
react.useEffect(() => {
|
|
1191
|
+
if (skip) {
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
if (observerRef.current !== null) {
|
|
1195
|
+
observerRef.current.disconnect();
|
|
1196
|
+
observerRef.current = null;
|
|
1197
|
+
}
|
|
1198
|
+
const observer = new ResizeObserver(() => {
|
|
1199
|
+
updateAllGeometries();
|
|
1200
|
+
});
|
|
1201
|
+
// Observe all current elements
|
|
1202
|
+
elementsRef.current.forEach(element => {
|
|
1203
|
+
observer.observe(element);
|
|
1204
|
+
});
|
|
1205
|
+
observerRef.current = observer;
|
|
1206
|
+
return () => {
|
|
1207
|
+
if (observerRef.current !== null) {
|
|
1208
|
+
observerRef.current.disconnect();
|
|
1209
|
+
observerRef.current = null;
|
|
1125
1210
|
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1211
|
+
};
|
|
1212
|
+
}, [skip, updateAllGeometries]);
|
|
1213
|
+
// Create stable ref callbacks per index
|
|
1214
|
+
const getRef = react.useCallback((index) => {
|
|
1215
|
+
const existing = refCallbacksRef.current.get(index);
|
|
1216
|
+
if (existing) {
|
|
1217
|
+
return existing;
|
|
1218
|
+
}
|
|
1219
|
+
const callback = (el) => {
|
|
1220
|
+
if (el) {
|
|
1221
|
+
elementsRef.current.set(index, el);
|
|
1222
|
+
// Observe this new element
|
|
1223
|
+
if (!skip && observerRef.current) {
|
|
1224
|
+
observerRef.current.observe(el);
|
|
1225
|
+
}
|
|
1226
|
+
// Trigger re-render to measure new element
|
|
1227
|
+
setElementCount(elementsRef.current.size);
|
|
1128
1228
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1229
|
+
else {
|
|
1230
|
+
elementsRef.current.delete(index);
|
|
1231
|
+
// Trigger re-render to update measurements
|
|
1232
|
+
setElementCount(elementsRef.current.size);
|
|
1233
|
+
}
|
|
1234
|
+
};
|
|
1235
|
+
refCallbacksRef.current.set(index, callback);
|
|
1236
|
+
return callback;
|
|
1237
|
+
}, [skip]);
|
|
1238
|
+
return { geometries, getRef };
|
|
1239
|
+
};
|
|
1240
|
+
|
|
1241
|
+
const DEFAULT_STATE = {
|
|
1242
|
+
phase: "uninitialized",
|
|
1243
|
+
visibleCount: 0,
|
|
1244
|
+
totalCount: 0,
|
|
1245
|
+
withCounter: false,
|
|
1246
|
+
multiValueGeometries: new Map(),
|
|
1247
|
+
availableWidthForTags: 0,
|
|
1248
|
+
};
|
|
1249
|
+
const RESERVED_TEXT_INPUT_WIDTH = 40;
|
|
1250
|
+
const GAP_BEFORE_COUNTER = 4;
|
|
1251
|
+
const GAP_BEFORE_TEXT_INPUT = 4;
|
|
1252
|
+
const measurementReducer = (state, action) => {
|
|
1253
|
+
switch (action.type) {
|
|
1254
|
+
case "UPDATE_GEOMETRIES": {
|
|
1255
|
+
// Only reset phase if the count meaningfully changed from the last completed measurement
|
|
1256
|
+
// Don't reset for hide/show cycles (0 size) - only when actual values added/removed
|
|
1257
|
+
const meaningfulCountChange = action.geometries.size > 0 && action.geometries.size !== state.totalCount;
|
|
1258
|
+
return {
|
|
1259
|
+
...state,
|
|
1260
|
+
multiValueGeometries: action.geometries,
|
|
1261
|
+
phase: meaningfulCountChange ? "measuring" : state.phase,
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
case "SET_AVAILABLE_WIDTH_FOR_TAGS": {
|
|
1265
|
+
// Only update if we have a valid width - prevents temporary 0-width states from breaking measurement
|
|
1266
|
+
const newWidth = action.availableWidthForTags > 0 ? action.availableWidthForTags : state.availableWidthForTags;
|
|
1267
|
+
// If width didn't actually change, don't update state
|
|
1268
|
+
if (newWidth === state.availableWidthForTags) {
|
|
1269
|
+
return state;
|
|
1141
1270
|
}
|
|
1142
1271
|
return {
|
|
1143
|
-
|
|
1144
|
-
|
|
1272
|
+
...state,
|
|
1273
|
+
availableWidthForTags: newWidth,
|
|
1274
|
+
phase: "measuring",
|
|
1145
1275
|
};
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1276
|
+
}
|
|
1277
|
+
case "SET_MEASUREMENTS": {
|
|
1278
|
+
return {
|
|
1279
|
+
...state,
|
|
1280
|
+
phase: action.phase,
|
|
1281
|
+
visibleCount: action.visibleCount,
|
|
1282
|
+
totalCount: action.totalCount,
|
|
1283
|
+
withCounter: action.withCounter,
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
case "RESET": {
|
|
1287
|
+
return DEFAULT_STATE;
|
|
1288
|
+
}
|
|
1289
|
+
case "CHANGE_PHASE": {
|
|
1290
|
+
return {
|
|
1291
|
+
...state,
|
|
1292
|
+
phase: action.phase,
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
default: {
|
|
1296
|
+
throw new Error(`${action} is not known`);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
};
|
|
1300
|
+
/**
|
|
1301
|
+
* Hook to manage multi-value overflow detection and rendering.
|
|
1302
|
+
* Measures which values fit in the container and determines when to show a counter.
|
|
1303
|
+
*/
|
|
1304
|
+
const useMultiValueOverflow = ({ skip = false }) => {
|
|
1305
|
+
const [measurementState, dispatch] = react.useReducer(measurementReducer, DEFAULT_STATE);
|
|
1306
|
+
const { ref: setValueContainerRef, geometry: containerGeometry } = reactComponents.useMeasure({
|
|
1307
|
+
skip: skip || measurementState.multiValueGeometries.size === 0,
|
|
1308
|
+
});
|
|
1309
|
+
const { ref: setCounterRef } = reactComponents.useMeasure({
|
|
1310
|
+
skip: skip || measurementState.multiValueGeometries.size === 0,
|
|
1311
|
+
});
|
|
1312
|
+
const { ref: setFakeCounterRef, geometry: fakeCounterGeometry } = reactComponents.useMeasure({
|
|
1313
|
+
skip: skip || measurementState.multiValueGeometries.size === 0,
|
|
1314
|
+
});
|
|
1315
|
+
const [menuElement, setMenuElement] = react.useState(null);
|
|
1316
|
+
const setMenuRef = react.useCallback((element) => {
|
|
1317
|
+
setMenuElement(element);
|
|
1318
|
+
}, []);
|
|
1319
|
+
const menuIsOpen = menuElement !== null;
|
|
1320
|
+
const { getRef: setGeometryRef } = useMultiMeasure({
|
|
1321
|
+
skip,
|
|
1322
|
+
onChange: geometries => {
|
|
1323
|
+
dispatch({ type: "UPDATE_GEOMETRIES", geometries });
|
|
1324
|
+
},
|
|
1325
|
+
});
|
|
1326
|
+
const availableWidthForTags = react.useMemo(() => {
|
|
1327
|
+
if (!containerGeometry || skip) {
|
|
1328
|
+
return 0;
|
|
1329
|
+
}
|
|
1330
|
+
const reservedInputWidth = menuIsOpen ? RESERVED_TEXT_INPUT_WIDTH : 0;
|
|
1331
|
+
let availableWidth = containerGeometry.width - reservedInputWidth - GAP_BEFORE_TEXT_INPUT;
|
|
1332
|
+
if (measurementState.phase === "measuring-with-counter") {
|
|
1333
|
+
availableWidth -= (fakeCounterGeometry?.width ?? 0) + GAP_BEFORE_COUNTER;
|
|
1334
|
+
}
|
|
1335
|
+
return Math.max(0, availableWidth);
|
|
1336
|
+
}, [containerGeometry, fakeCounterGeometry?.width, measurementState.phase, menuIsOpen, skip]);
|
|
1337
|
+
reactComponents.useDebounce(availableWidthForTags, {
|
|
1338
|
+
onBounce: debouncedAvailableWidthForTags => {
|
|
1339
|
+
dispatch({ type: "SET_AVAILABLE_WIDTH_FOR_TAGS", availableWidthForTags: debouncedAvailableWidthForTags });
|
|
1340
|
+
},
|
|
1341
|
+
delay: 60,
|
|
1342
|
+
});
|
|
1343
|
+
// Measurement logic
|
|
1344
|
+
react.useLayoutEffect(() => {
|
|
1345
|
+
if (skip) {
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
const hasInvalidMultiItemGeometry = Array.from(measurementState.multiValueGeometries.values()).some(
|
|
1349
|
+
// Don't measure if any geometry has invalid dimensions (not yet laid out)
|
|
1350
|
+
geo => geo.width === 0 || geo.height === 0);
|
|
1351
|
+
if (!containerGeometry ||
|
|
1352
|
+
containerGeometry.width === 0 ||
|
|
1353
|
+
measurementState.multiValueGeometries.size === 0 ||
|
|
1354
|
+
measurementState.availableWidthForTags <= 0 ||
|
|
1355
|
+
hasInvalidMultiItemGeometry) {
|
|
1356
|
+
// Early return
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
const totalCount = measurementState.multiValueGeometries.size;
|
|
1360
|
+
switch (measurementState.phase) {
|
|
1361
|
+
case "uninitialized": {
|
|
1362
|
+
dispatch({ type: "CHANGE_PHASE", phase: "measuring" });
|
|
1363
|
+
break;
|
|
1364
|
+
}
|
|
1365
|
+
case "measuring": {
|
|
1366
|
+
const visibleCount = getVisibleCountFromGeometries({
|
|
1367
|
+
geometries: measurementState.multiValueGeometries,
|
|
1368
|
+
availableWidth: measurementState.availableWidthForTags,
|
|
1369
|
+
containerX: containerGeometry.x,
|
|
1370
|
+
totalCount,
|
|
1371
|
+
});
|
|
1372
|
+
if (visibleCount === totalCount) {
|
|
1373
|
+
dispatch({ type: "SET_MEASUREMENTS", totalCount, visibleCount, withCounter: false, phase: "complete" });
|
|
1374
|
+
}
|
|
1375
|
+
else {
|
|
1376
|
+
dispatch({
|
|
1377
|
+
type: "SET_MEASUREMENTS",
|
|
1378
|
+
totalCount,
|
|
1379
|
+
visibleCount,
|
|
1380
|
+
withCounter: true,
|
|
1381
|
+
phase: "measuring-with-counter",
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
break;
|
|
1385
|
+
}
|
|
1386
|
+
case "measuring-with-counter": {
|
|
1387
|
+
if (!fakeCounterGeometry || fakeCounterGeometry.width === 0) {
|
|
1388
|
+
// Wait another render cycle for the fake counter to be measured
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
const visibleCount = getVisibleCountFromGeometries({
|
|
1392
|
+
geometries: measurementState.multiValueGeometries,
|
|
1393
|
+
availableWidth: measurementState.availableWidthForTags,
|
|
1394
|
+
containerX: containerGeometry.x,
|
|
1395
|
+
totalCount,
|
|
1396
|
+
});
|
|
1397
|
+
dispatch({ type: "SET_MEASUREMENTS", totalCount, visibleCount, withCounter: true, phase: "complete" });
|
|
1398
|
+
break;
|
|
1399
|
+
}
|
|
1400
|
+
case "complete": {
|
|
1401
|
+
break;
|
|
1402
|
+
}
|
|
1403
|
+
default: {
|
|
1404
|
+
throw new Error(`${measurementState.phase} is not known`);
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
}, [
|
|
1408
|
+
containerGeometry,
|
|
1409
|
+
containerGeometry?.width,
|
|
1410
|
+
fakeCounterGeometry,
|
|
1411
|
+
fakeCounterGeometry?.width,
|
|
1412
|
+
measurementState,
|
|
1413
|
+
measurementState.phase,
|
|
1414
|
+
measurementState.totalCount,
|
|
1415
|
+
measurementState.visibleCount,
|
|
1416
|
+
skip,
|
|
1417
|
+
]);
|
|
1418
|
+
// Store current values in a ref so getter functions can stay stable
|
|
1419
|
+
const valuesRef = react.useRef({
|
|
1420
|
+
visibleCount: measurementState.visibleCount,
|
|
1421
|
+
totalCount: measurementState.totalCount,
|
|
1422
|
+
withCounter: measurementState.withCounter,
|
|
1423
|
+
isComplete: measurementState.phase === "complete",
|
|
1424
|
+
});
|
|
1425
|
+
// Use state setter to trigger rerenders when values change
|
|
1426
|
+
// The setter itself is stable, but calling it triggers a rerender
|
|
1427
|
+
const [, setVersion] = react.useState(0);
|
|
1428
|
+
// Update ref and trigger rerender when state changes
|
|
1429
|
+
react.useEffect(() => {
|
|
1430
|
+
valuesRef.current = {
|
|
1431
|
+
visibleCount: measurementState.visibleCount,
|
|
1432
|
+
totalCount: measurementState.totalCount,
|
|
1433
|
+
withCounter: measurementState.withCounter,
|
|
1434
|
+
isComplete: measurementState.phase === "complete",
|
|
1435
|
+
};
|
|
1436
|
+
// Trigger rerender so components can read new values from getters
|
|
1437
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
1438
|
+
setVersion(prev => prev + 1);
|
|
1439
|
+
}, [
|
|
1440
|
+
measurementState.visibleCount,
|
|
1441
|
+
measurementState.totalCount,
|
|
1442
|
+
measurementState.withCounter,
|
|
1443
|
+
measurementState.phase,
|
|
1444
|
+
]);
|
|
1445
|
+
// Create stable getter functions that read from the ref
|
|
1446
|
+
// These functions stay stable (same reference) but always return the latest values
|
|
1447
|
+
const getVisibleCount = react.useCallback(() => valuesRef.current.visibleCount, []);
|
|
1448
|
+
const getTotalCount = react.useCallback(() => valuesRef.current.totalCount, []);
|
|
1449
|
+
const getCounterWidth = react.useCallback(() => valuesRef.current.withCounter, []);
|
|
1450
|
+
const getIsComplete = react.useCallback(() => valuesRef.current.isComplete, []);
|
|
1451
|
+
return react.useMemo(() => ({
|
|
1452
|
+
setValueContainerRef,
|
|
1453
|
+
setCounterRef,
|
|
1454
|
+
setFakeCounterRef,
|
|
1455
|
+
setGeometryRef,
|
|
1456
|
+
setMenuRef,
|
|
1457
|
+
getVisibleCount,
|
|
1458
|
+
getTotalCount,
|
|
1459
|
+
getCounterWidth,
|
|
1460
|
+
getIsComplete,
|
|
1461
|
+
}), [
|
|
1462
|
+
setValueContainerRef,
|
|
1463
|
+
setCounterRef,
|
|
1464
|
+
setFakeCounterRef,
|
|
1465
|
+
setGeometryRef,
|
|
1466
|
+
setMenuRef,
|
|
1467
|
+
getVisibleCount,
|
|
1468
|
+
getTotalCount,
|
|
1469
|
+
getCounterWidth,
|
|
1470
|
+
getIsComplete,
|
|
1471
|
+
]);
|
|
1472
|
+
};
|
|
1473
|
+
const getVisibleCountFromGeometries = ({ geometries, availableWidth, containerX, totalCount, }) => {
|
|
1474
|
+
const firstGeometry = geometries.get(0);
|
|
1475
|
+
if (firstGeometry === undefined) {
|
|
1476
|
+
throw new Error("First multiValue geometry is not available, this should never happen");
|
|
1477
|
+
}
|
|
1478
|
+
let visibleCount = 0;
|
|
1479
|
+
for (let i = 0; i < totalCount; i++) {
|
|
1480
|
+
const geometry = geometries.get(i);
|
|
1481
|
+
if (geometry === undefined) {
|
|
1482
|
+
throw new Error(`MultiValue geometry at index ${i} is not available, this should never happen`);
|
|
1483
|
+
}
|
|
1484
|
+
const distanceFromFirst = geometry.x + geometry.width - containerX;
|
|
1485
|
+
const fits = distanceFromFirst <= availableWidth;
|
|
1486
|
+
if (fits) {
|
|
1487
|
+
visibleCount = i + 1; //+1 because we want to count, not get the index
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
return visibleCount;
|
|
1152
1491
|
};
|
|
1153
1492
|
|
|
1154
1493
|
/**
|
|
1155
1494
|
* A hook to retrieve components override object.
|
|
1156
1495
|
* This complex object includes all the compositional components that are used in react-select. If you wish to overwrite a component, pass in an object with the appropriate namespace.
|
|
1157
1496
|
*
|
|
1158
|
-
* @template
|
|
1159
|
-
* @template
|
|
1160
|
-
* @
|
|
1161
|
-
* @param {
|
|
1162
|
-
* @
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
* @returns {Partial<SelectComponents<Option, boolean, GroupBase<Option>>> | undefined} components object to override react-select default components
|
|
1168
|
-
*/
|
|
1169
|
-
const useCustomComponents = ({ componentsProps, disabled, readOnly, setMenuIsEnabled, "data-testid": dataTestId, maxSelectedDisplayCount, prefix, hasError, fieldSize, getOptionLabelDescription, getOptionPrefix, }) => {
|
|
1497
|
+
* @template TOption
|
|
1498
|
+
* @template TIsMulti
|
|
1499
|
+
* @template TGroup
|
|
1500
|
+
* @param {CustomComponentsProps<TOption>} props - The custom components props
|
|
1501
|
+
* @returns {Partial<SelectComponents<TOption, TIsMulti, TGroup>>} components object to override react-select default components
|
|
1502
|
+
*/
|
|
1503
|
+
const useCustomComponents = ({ disabled, readOnly, "data-testid": dataTestId, prefix, hasError, fieldSize = "medium", getOptionLabelDescription, getOptionPrefix, className, isMulti, //prefer using the component prop (ala. selectValueContainer.isMulti) inside of customComponents instead of this one.
|
|
1504
|
+
autoComplete, // see https://github.com/JedWatson/react-select/issues/758
|
|
1505
|
+
}) => {
|
|
1170
1506
|
const [t] = useTranslation();
|
|
1507
|
+
const { setValueContainerRef, setCounterRef, setFakeCounterRef, setGeometryRef, setMenuRef, getVisibleCount, getTotalCount, getCounterWidth, getIsComplete, } = useMultiValueOverflow({ skip: !isMulti });
|
|
1508
|
+
const interactable = react.useMemo(() => !Boolean(disabled) && !Boolean(readOnly), [disabled, readOnly]);
|
|
1171
1509
|
// perhaps it should not be wrap in memo (causing some issues with opening and closing on mobiles)
|
|
1510
|
+
// Component functions stay stable (reading from refs), so react-select won't lose focus
|
|
1172
1511
|
const customComponents = react.useMemo(() => {
|
|
1173
1512
|
return {
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
},
|
|
1198
|
-
disabled: disabled,
|
|
1199
|
-
Icon: optionPrefix,
|
|
1200
|
-
};
|
|
1201
|
-
})
|
|
1202
|
-
: [], postFix: searchInput, preFix: placeholderElement ? jsxRuntime.jsx("span", { className: "absolute", children: placeholderElement }) : null, width: "100%" })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [tags
|
|
1203
|
-
? tags.slice(0, maxSelectedDisplayCount).map(({ props: tagProps }) => {
|
|
1204
|
-
return (jsxRuntime.jsx(reactComponents.Tag, { className: "inline-flex shrink-0", color: disabled ? "unknown" : "primary", "data-testid": tagProps.children ? `${tagProps.children.toString()}-tag` : undefined, onClose: e => {
|
|
1205
|
-
e.stopPropagation();
|
|
1206
|
-
setMenuIsEnabled(false);
|
|
1207
|
-
tagProps.removeProps.onClick && tagProps.removeProps.onClick(e);
|
|
1208
|
-
}, children: tagProps.children }, tagProps.children?.toString()));
|
|
1209
|
-
})
|
|
1210
|
-
: null, tags && tags.length > maxSelectedDisplayCount ? (jsxRuntime.jsxs(reactComponents.Tag, { color: "neutral", "data-testid": "counter-tag", children: ["+", tags.length - maxSelectedDisplayCount] })) : null, searchInput, placeholderElement] })) }));
|
|
1211
|
-
}
|
|
1212
|
-
return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsx(ReactSelect.components.ValueContainer, { ...props, isDisabled: props.selectProps.isDisabled, children: props.children }) }));
|
|
1513
|
+
SelectContainer: selectContainer => {
|
|
1514
|
+
return (jsxRuntime.jsx(ReactSelect.components.SelectContainer, { ...selectContainer, className: cvaSelectContainer({
|
|
1515
|
+
invalid: hasError,
|
|
1516
|
+
fieldSize,
|
|
1517
|
+
disabled: Boolean(disabled),
|
|
1518
|
+
className: [className, selectContainer.className],
|
|
1519
|
+
readOnly: Boolean(readOnly),
|
|
1520
|
+
focused: selectContainer.isFocused,
|
|
1521
|
+
}), getStyles: getNoStyles, innerProps: {
|
|
1522
|
+
...selectContainer.innerProps,
|
|
1523
|
+
...(dataTestId ? { "data-testid": dataTestId } : {}),
|
|
1524
|
+
}, children: selectContainer.children }));
|
|
1525
|
+
},
|
|
1526
|
+
Control: selectControl => {
|
|
1527
|
+
return (jsxRuntime.jsxs(ReactSelect.components.Control, { ...selectControl, className: cvaSelectControl({
|
|
1528
|
+
className: selectControl.className,
|
|
1529
|
+
}), getStyles: getNoStyles, innerProps: Boolean(readOnly)
|
|
1530
|
+
? // We omit the onMouseDown and onTouchEnd events to allow text selection
|
|
1531
|
+
esToolkit.omit(selectControl.innerProps, ["onMouseDown", "onTouchEnd"])
|
|
1532
|
+
: selectControl.innerProps, children: [prefix !== undefined ? (jsxRuntime.jsx("div", { className: cvaSelectPrefixSuffix(), "data-testid": dataTestId ? `${dataTestId}-prefix` : null, children: prefix })) : null, selectControl.children, typeof disabled === "object" ? (jsxRuntime.jsx("div", { className: cvaSelectPrefixSuffix(), "data-testid": dataTestId ? `${dataTestId}-disabled-locked` : null, children: jsxRuntime.jsx(InputLockReasonTooltip, { ...disabled }) })) : null, typeof readOnly === "object" && !Boolean(disabled) ? (jsxRuntime.jsx("div", { className: cvaSelectPrefixSuffix(), "data-testid": dataTestId ? `${dataTestId}-readonly-locked` : null, children: jsxRuntime.jsx(InputLockReasonTooltip, { ...readOnly }) })) : null] }));
|
|
1533
|
+
},
|
|
1534
|
+
Placeholder: selectPlaceholder => {
|
|
1535
|
+
return (jsxRuntime.jsx(ReactSelect.components.Placeholder, { ...selectPlaceholder, className: cvaSelectPlaceholder({ className: selectPlaceholder.className }), getStyles: getNoStyles, children: selectPlaceholder.children }));
|
|
1213
1536
|
},
|
|
1214
|
-
LoadingIndicator:
|
|
1215
|
-
return jsxRuntime.jsx(reactComponents.Spinner, { centering: "vertically",
|
|
1537
|
+
LoadingIndicator: selectLoadingIndicator => {
|
|
1538
|
+
return jsxRuntime.jsx(reactComponents.Spinner, { ...selectLoadingIndicator.innerProps, centering: "vertically", size: "small" });
|
|
1216
1539
|
},
|
|
1217
|
-
DropdownIndicator:
|
|
1218
|
-
|
|
1219
|
-
|
|
1540
|
+
DropdownIndicator: selectDropdownIndicator => {
|
|
1541
|
+
if (!interactable || selectDropdownIndicator.selectProps.isLoading) {
|
|
1542
|
+
return null;
|
|
1543
|
+
}
|
|
1544
|
+
return (jsxRuntime.jsx(ReactSelect.components.DropdownIndicator, { ...selectDropdownIndicator, className: cvaSelectDropdownIconContainer({ className: selectDropdownIndicator.className }), getStyles: getNoStyles, children: jsxRuntime.jsx(reactComponents.Icon, { className: cvaSelectDropdownIndicator({ menuIsOpen: selectDropdownIndicator.selectProps.menuIsOpen }), name: "ChevronDown", size: "medium" }) }));
|
|
1545
|
+
},
|
|
1546
|
+
// --------------------------------
|
|
1547
|
+
// Inside Popover👇
|
|
1548
|
+
// --------------------------------
|
|
1549
|
+
ValueContainer: selectValueContainer => {
|
|
1550
|
+
// Read latest overflow values using getters (functions stay stable)
|
|
1551
|
+
const currentVisibleCount = getVisibleCount();
|
|
1552
|
+
const currentTotalCount = getTotalCount();
|
|
1553
|
+
const currentWithCounter = getCounterWidth();
|
|
1554
|
+
const currentIsComplete = getIsComplete();
|
|
1555
|
+
return (jsxRuntime.jsx(ReactSelect.components.ValueContainer, { ...selectValueContainer, className: cvaSelectValueContainer({ className: selectValueContainer.className }), getStyles: getNoStyles, innerProps: {
|
|
1556
|
+
...selectValueContainer.innerProps,
|
|
1557
|
+
ref: reactComponents.useMergeRefs([setValueContainerRef, selectValueContainer.innerProps?.ref]),
|
|
1558
|
+
}, children: selectValueContainer.isMulti ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [getPlaceholderElement(selectValueContainer.children), currentWithCounter && !currentIsComplete ? (
|
|
1559
|
+
// Render the test-counter-tag in the beginning to make sure it's included in the calculation
|
|
1560
|
+
// will be removed when the calculation is complete
|
|
1561
|
+
jsxRuntime.jsx(CounterTag, { className: "invisible" // Is invisible because we're only putting it here to measure the width
|
|
1562
|
+
, "data-testid": "fake-multiselect-counter", fieldSize: fieldSize, hiddenCount: currentTotalCount - currentVisibleCount, ref: setFakeCounterRef, totalCount: currentTotalCount })) : null, getMultiValueComponents(selectValueContainer.children), currentWithCounter && currentIsComplete ? (
|
|
1563
|
+
// This is the actual tag that will be visible in the UI
|
|
1564
|
+
jsxRuntime.jsx(CounterTag, { "data-testid": dataTestId ? `${dataTestId}-multiselect-counter` : "multiselect-counter", fieldSize: fieldSize, hiddenCount: currentTotalCount - currentVisibleCount, ref: setCounterRef, totalCount: currentTotalCount })) : null, getInputComponent(selectValueContainer.children)] })) : (selectValueContainer.children) }));
|
|
1220
1565
|
},
|
|
1221
1566
|
IndicatorSeparator: () => null,
|
|
1222
|
-
|
|
1223
|
-
|
|
1567
|
+
Input: selectInput => {
|
|
1568
|
+
return jsxRuntime.jsx(ReactSelect.components.Input, { ...selectInput, autoComplete: autoComplete });
|
|
1569
|
+
},
|
|
1570
|
+
IndicatorsContainer: selectIndicatorsContainer => {
|
|
1571
|
+
return (jsxRuntime.jsx(ReactSelect.components.IndicatorsContainer, { ...selectIndicatorsContainer, className: cvaSelectIndicatorsContainer({ className: selectIndicatorsContainer.className }), getStyles: getNoStyles, children: selectIndicatorsContainer.children }));
|
|
1572
|
+
},
|
|
1573
|
+
ClearIndicator: selectClearIndicator => {
|
|
1574
|
+
if (Boolean(disabled)) {
|
|
1224
1575
|
return null;
|
|
1225
1576
|
}
|
|
1226
|
-
return (jsxRuntime.jsx(ReactSelect.components.ClearIndicator, { ...
|
|
1227
|
-
...props.innerProps,
|
|
1228
|
-
onMouseDown: e => {
|
|
1229
|
-
e.preventDefault();
|
|
1230
|
-
},
|
|
1231
|
-
}, children: jsxRuntime.jsx("div", { className: cvaSelectXIcon(), "data-testid": dataTestId ? `${dataTestId}-XMarkIcon` : null, onClick: props.clearValue, children: jsxRuntime.jsx(reactComponents.Icon, { ariaLabel: t("clearIndicator.icon.tooltip.clearAll"), name: "XCircle", size: "medium" }) }) }));
|
|
1577
|
+
return (jsxRuntime.jsx(ReactSelect.components.ClearIndicator, { ...selectClearIndicator, className: cvaSelectClearIndicator({ className: selectClearIndicator.className }), getStyles: getNoStyles, children: jsxRuntime.jsx(reactComponents.Icon, { ariaLabel: t("clearIndicator.icon.tooltip.clearAll"), "data-testid": `${dataTestId}-XMarkIcon`, name: "XCircle", size: "medium" }) }));
|
|
1232
1578
|
},
|
|
1233
|
-
|
|
1234
|
-
return (jsxRuntime.jsx(ReactSelect.components.
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1579
|
+
LoadingMessage: selectLoadingMessage => {
|
|
1580
|
+
return (jsxRuntime.jsx(ReactSelect.components.LoadingMessage, { ...selectLoadingMessage, className: cvaSelectLoadingMessage({ className: selectLoadingMessage.className }), getStyles: getNoStyles, children: t("select.loadingMessage") }));
|
|
1581
|
+
},
|
|
1582
|
+
NoOptionsMessage: selectNoOptionsMessage => {
|
|
1583
|
+
return (jsxRuntime.jsx(ReactSelect.components.NoOptionsMessage, { ...selectNoOptionsMessage, className: cvaSelectNoOptionsMessage({ className: selectNoOptionsMessage.className }), getStyles: getNoStyles, children: t("select.noOptionsMessage") }));
|
|
1584
|
+
},
|
|
1585
|
+
SingleValue: selectSingleValue => {
|
|
1586
|
+
const optionPrefix = getOptionPrefix ? getOptionPrefix(selectSingleValue.data) : null;
|
|
1587
|
+
return (jsxRuntime.jsx(ReactSelect.components.SingleValue, { ...selectSingleValue, className: cvaSelectSingleValue({
|
|
1588
|
+
className: selectSingleValue.className,
|
|
1589
|
+
}), getStyles: getNoStyles, children: jsxRuntime.jsxs("div", { className: "flex items-center gap-1", "data-testid": dataTestId + "-singleValue", children: [optionPrefix !== null ? optionPrefix : null, selectSingleValue.children, getOptionLabelDescription && getOptionLabelDescription(selectSingleValue.data) ? (jsxRuntime.jsxs("span", { className: "ml-1 text-neutral-400", children: ["(", getOptionLabelDescription(selectSingleValue.data), ")"] })) : null] }) }));
|
|
1239
1590
|
},
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1591
|
+
MultiValueContainer: ({ children }) => children, // Just pass on the children
|
|
1592
|
+
MultiValueRemove: () => null, // is built-in to the MultiValue (tag) component
|
|
1593
|
+
MultiValueLabel: ({ children }) => children, // Just pass on the children
|
|
1594
|
+
MultiValue: selectMultiValue => {
|
|
1595
|
+
// Read latest overflow values using getters (functions stay stable)
|
|
1596
|
+
const currentVisibleCount = getVisibleCount();
|
|
1597
|
+
const currentWithCounter = getCounterWidth();
|
|
1598
|
+
const currentIsComplete = getIsComplete();
|
|
1599
|
+
const index = selectMultiValue.index;
|
|
1600
|
+
return (jsxRuntime.jsx(ReactSelect.components.MultiValue, { ...selectMultiValue, getStyles: getNoStyles, children: jsxRuntime.jsx(MultiValue, { className: cvaSelectMultiValue({
|
|
1601
|
+
hidden: currentIsComplete && index + 1 > currentVisibleCount, // Hide if doesn't fit
|
|
1602
|
+
invisible: currentWithCounter && !currentIsComplete, // Make invisible if measuring with counter. When there's a counter, it would otherwise briefly change layout to make room for the "fake" counter
|
|
1603
|
+
}), data: selectMultiValue.data, "data-testid": dataTestId ? `${dataTestId}-multiValue-${index}` : undefined, disabled: Boolean(disabled), fieldSize: fieldSize, getOptionPrefix: getOptionPrefix, onClose: selectMultiValue.removeProps.onClick, ref: setGeometryRef(index), children: selectMultiValue.children }) }));
|
|
1243
1604
|
},
|
|
1244
|
-
Menu:
|
|
1245
|
-
return (jsxRuntime.jsx(ReactSelect.components.Menu, { ...
|
|
1605
|
+
Menu: selectMenu => {
|
|
1606
|
+
return (jsxRuntime.jsx(ReactSelect.components.Menu, { ...selectMenu, className: cvaSelectMenu({ className: selectMenu.className, placement: selectMenu.placement }), getStyles: getNoStyles, innerProps: {
|
|
1607
|
+
...selectMenu.innerProps,
|
|
1608
|
+
ref: reactComponents.useMergeRefs([setMenuRef, selectMenu.innerProps.ref]),
|
|
1609
|
+
} }));
|
|
1246
1610
|
},
|
|
1247
|
-
|
|
1248
|
-
return (jsxRuntime.jsx(ReactSelect.components.
|
|
1611
|
+
MenuPortal: selectMenuPortal => {
|
|
1612
|
+
return (jsxRuntime.jsx(ReactSelect.components.MenuPortal, { ...selectMenuPortal, className: "!z-overlay", children: selectMenuPortal.children }));
|
|
1249
1613
|
},
|
|
1250
|
-
MenuList:
|
|
1251
|
-
return (jsxRuntime.jsx(ReactSelect.components.MenuList, {
|
|
1252
|
-
|
|
1614
|
+
MenuList: selectMenuList => {
|
|
1615
|
+
return (jsxRuntime.jsx(ReactSelect.components.MenuList, { className: cvaSelectMenuList({
|
|
1616
|
+
className: selectMenuList.className,
|
|
1617
|
+
}), ...selectMenuList, getStyles: getNoStyles, innerProps: {
|
|
1618
|
+
...selectMenuList.innerProps,
|
|
1253
1619
|
onScroll: e => {
|
|
1254
1620
|
const listEl = e.currentTarget;
|
|
1255
1621
|
if (listEl.scrollTop + listEl.clientHeight >= listEl.scrollHeight &&
|
|
1256
|
-
|
|
1257
|
-
/Firefox/.test(navigator.userAgent)
|
|
1258
|
-
|
|
1259
|
-
|
|
1622
|
+
selectMenuList.selectProps.onMenuScrollToBottom) {
|
|
1623
|
+
if (/Firefox/.test(navigator.userAgent)) {
|
|
1624
|
+
selectMenuList.selectProps.onMenuScrollToBottom(new WheelEvent("scroll"));
|
|
1625
|
+
}
|
|
1626
|
+
else {
|
|
1627
|
+
selectMenuList.selectProps.onMenuScrollToBottom(new TouchEvent(""));
|
|
1628
|
+
}
|
|
1260
1629
|
}
|
|
1261
1630
|
},
|
|
1262
|
-
}, children:
|
|
1631
|
+
}, children: selectMenuList.children }));
|
|
1263
1632
|
},
|
|
1264
|
-
Option:
|
|
1633
|
+
Option: selectOption => {
|
|
1265
1634
|
const componentProps = {
|
|
1266
|
-
label:
|
|
1267
|
-
focused:
|
|
1268
|
-
selected:
|
|
1269
|
-
onClick:
|
|
1635
|
+
label: selectOption.label,
|
|
1636
|
+
focused: selectOption.isFocused,
|
|
1637
|
+
selected: selectOption.isSelected,
|
|
1638
|
+
onClick: selectOption.innerProps.onClick,
|
|
1270
1639
|
};
|
|
1271
|
-
return (jsxRuntime.jsx(ReactSelect.components.Option, { ...
|
|
1272
|
-
...
|
|
1640
|
+
return (jsxRuntime.jsx(ReactSelect.components.Option, { ...selectOption, innerProps: {
|
|
1641
|
+
...selectOption.innerProps,
|
|
1273
1642
|
role: "option",
|
|
1274
|
-
|
|
1275
|
-
}, children: props.isMulti ? (jsxRuntime.jsx(MultiSelectMenuItem, { ...componentProps, "data-testid": typeof props.label === "string" ? props.label : undefined, disabled: disabled, fieldSize: fieldSize, optionLabelDescription: getOptionLabelDescription?.(props.data), optionPrefix: getOptionPrefix?.(props.data) })) : (jsxRuntime.jsx(SingleSelectMenuItem, { ...componentProps, "data-testid": typeof props.label === "string" ? props.label : undefined, disabled: disabled || props.isDisabled, fieldSize: fieldSize, optionLabelDescription: getOptionLabelDescription?.(props.data), optionPrefix: getOptionPrefix?.(props.data) })) }));
|
|
1643
|
+
}, children: selectOption.isMulti ? (jsxRuntime.jsx(MultiSelectMenuItem, { ...componentProps, "data-testid": typeof selectOption.label === "string" ? selectOption.label : undefined, disabled: Boolean(disabled), fieldSize: fieldSize, optionLabelDescription: getOptionLabelDescription?.(selectOption.data), optionPrefix: getOptionPrefix?.(selectOption.data) })) : (jsxRuntime.jsx(SingleSelectMenuItem, { ...componentProps, "data-testid": typeof selectOption.label === "string" ? selectOption.label : undefined, disabled: Boolean(disabled) || selectOption.isDisabled, fieldSize: fieldSize, optionLabelDescription: getOptionLabelDescription?.(selectOption.data), optionPrefix: getOptionPrefix?.(selectOption.data) })) }));
|
|
1276
1644
|
},
|
|
1277
|
-
...componentsProps,
|
|
1278
1645
|
};
|
|
1279
1646
|
}, [
|
|
1280
|
-
|
|
1281
|
-
|
|
1647
|
+
hasError,
|
|
1648
|
+
fieldSize,
|
|
1282
1649
|
disabled,
|
|
1283
|
-
|
|
1650
|
+
className,
|
|
1284
1651
|
readOnly,
|
|
1285
1652
|
dataTestId,
|
|
1286
|
-
t,
|
|
1287
1653
|
prefix,
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1654
|
+
interactable,
|
|
1655
|
+
getVisibleCount,
|
|
1656
|
+
getTotalCount,
|
|
1657
|
+
getCounterWidth,
|
|
1658
|
+
getIsComplete,
|
|
1659
|
+
setValueContainerRef,
|
|
1660
|
+
setFakeCounterRef,
|
|
1661
|
+
setCounterRef,
|
|
1662
|
+
autoComplete,
|
|
1663
|
+
t,
|
|
1291
1664
|
getOptionPrefix,
|
|
1665
|
+
getOptionLabelDescription,
|
|
1666
|
+
setGeometryRef,
|
|
1667
|
+
setMenuRef,
|
|
1292
1668
|
]);
|
|
1293
1669
|
return customComponents;
|
|
1294
1670
|
};
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
const
|
|
1305
|
-
|
|
1306
|
-
return
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
singleValue: base => ({
|
|
1316
|
-
...base,
|
|
1317
|
-
}),
|
|
1318
|
-
multiValue: base => ({
|
|
1319
|
-
...base,
|
|
1320
|
-
}),
|
|
1321
|
-
multiValueLabel: base => ({
|
|
1322
|
-
...base,
|
|
1323
|
-
}),
|
|
1324
|
-
indicatorsContainer: base => ({
|
|
1325
|
-
...base,
|
|
1326
|
-
...(disabled && { display: "none" }),
|
|
1327
|
-
}),
|
|
1328
|
-
indicatorSeparator: () => ({
|
|
1329
|
-
width: "0px",
|
|
1330
|
-
}),
|
|
1331
|
-
menu: base => {
|
|
1332
|
-
return {
|
|
1333
|
-
...base,
|
|
1334
|
-
width: "100%",
|
|
1335
|
-
marginTop: "4px",
|
|
1336
|
-
marginBottom: "18px",
|
|
1337
|
-
transition: "all 1s ease-in-out",
|
|
1338
|
-
};
|
|
1339
|
-
},
|
|
1340
|
-
input: base => ({
|
|
1341
|
-
...base,
|
|
1342
|
-
marginLeft: "0px",
|
|
1343
|
-
}),
|
|
1344
|
-
placeholder: base => ({
|
|
1345
|
-
...base,
|
|
1346
|
-
}),
|
|
1347
|
-
option: () => ({}),
|
|
1348
|
-
menuPortal: base => ({
|
|
1349
|
-
...base,
|
|
1350
|
-
width: refContainer.current ? `${refContainer.current.clientWidth}px` : base.width,
|
|
1351
|
-
backgroundColor: "#ffffff",
|
|
1352
|
-
borderRadius: "var(--border-radius-lg)",
|
|
1353
|
-
zIndex: "var(--z-overlay)",
|
|
1354
|
-
borderColor: "rgb(var(--color-neutral-300))",
|
|
1355
|
-
boxShadow: "var(--tw-ring-inset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)",
|
|
1356
|
-
}),
|
|
1357
|
-
menuList: base => {
|
|
1358
|
-
return {
|
|
1359
|
-
...base,
|
|
1360
|
-
position: "relative",
|
|
1361
|
-
padding: "var(--spacing-1)",
|
|
1362
|
-
display: "grid",
|
|
1363
|
-
gap: "var(--spacing-1)",
|
|
1364
|
-
width: "100%",
|
|
1365
|
-
borderRadius: "0px",
|
|
1366
|
-
boxShadow: "none",
|
|
1367
|
-
paddingTop: "0px",
|
|
1368
|
-
};
|
|
1369
|
-
},
|
|
1370
|
-
valueContainer: base => {
|
|
1371
|
-
return {
|
|
1372
|
-
...base,
|
|
1373
|
-
paddingBlock: 0,
|
|
1374
|
-
flexWrap: maxSelectedDisplayCount !== undefined ? "wrap" : "nowrap",
|
|
1375
|
-
gap: "0.25rem",
|
|
1376
|
-
};
|
|
1377
|
-
},
|
|
1378
|
-
container: base => ({
|
|
1379
|
-
...base,
|
|
1380
|
-
width: "100%",
|
|
1381
|
-
}),
|
|
1382
|
-
dropdownIndicator: base => ({
|
|
1383
|
-
...base,
|
|
1384
|
-
padding: "0px",
|
|
1385
|
-
}),
|
|
1386
|
-
clearIndicator: base => {
|
|
1387
|
-
return {
|
|
1388
|
-
...base,
|
|
1389
|
-
padding: "0px",
|
|
1390
|
-
};
|
|
1391
|
-
},
|
|
1392
|
-
...styles,
|
|
1393
|
-
};
|
|
1394
|
-
}, [refContainer, disabled, fieldSize, maxSelectedDisplayCount, styles]);
|
|
1395
|
-
return { customStyles };
|
|
1671
|
+
const getNoStyles = () => {
|
|
1672
|
+
// To prevent the default styles from being applied from react-select.
|
|
1673
|
+
return {};
|
|
1674
|
+
};
|
|
1675
|
+
const getInputComponent = (children) => {
|
|
1676
|
+
return Array.isArray(children)
|
|
1677
|
+
? children.find(child => child !== null && child !== undefined && /react-select-\d+-input/.test(child?.props?.id))
|
|
1678
|
+
: null;
|
|
1679
|
+
};
|
|
1680
|
+
const getMultiValueComponents = (children) => {
|
|
1681
|
+
if (!Array.isArray(children)) {
|
|
1682
|
+
return null;
|
|
1683
|
+
}
|
|
1684
|
+
// Only return if children[0] is an array (the multiValue array)
|
|
1685
|
+
return Array.isArray(children[0]) ? children[0] : null;
|
|
1686
|
+
};
|
|
1687
|
+
const getPlaceholderElement = (children) => {
|
|
1688
|
+
return Array.isArray(children)
|
|
1689
|
+
? children.find(child => child !== null && child !== undefined && child.key === "placeholder")
|
|
1690
|
+
: null;
|
|
1396
1691
|
};
|
|
1397
1692
|
|
|
1398
1693
|
/**
|
|
1399
1694
|
* A hook used by selects to share the common code
|
|
1400
1695
|
*
|
|
1401
1696
|
* @param {SelectProps} props - The props for the Select component
|
|
1402
|
-
* @returns {
|
|
1403
|
-
*/
|
|
1404
|
-
const useSelect = ({
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
fieldSize,
|
|
1412
|
-
});
|
|
1413
|
-
const [menuIsOpen, setMenuIsOpen] = react.useState(props.menuIsOpen ?? false);
|
|
1414
|
-
const [menuIsEnabled, setMenuIsEnabled] = react.useState(true);
|
|
1697
|
+
* @returns {ReactSelectProps} Props for react-select component
|
|
1698
|
+
*/
|
|
1699
|
+
const useSelect = ({ disabled = false, //renaming to isDisabled, so not directly passed to react-select
|
|
1700
|
+
"data-testid": dataTestId,
|
|
1701
|
+
// Extract critical props that must trigger recalculation when they change
|
|
1702
|
+
onMenuOpen, onMenuClose, options, value, onChange, defaultValue,
|
|
1703
|
+
// restProps are pur in a ref to keep dependencies stable while always having the latest values
|
|
1704
|
+
// See explanation on restPropsRef below for more details 👇
|
|
1705
|
+
...restProps }) => {
|
|
1415
1706
|
const customComponents = useCustomComponents({
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1707
|
+
disabled, // intentionally not evaluated as boolean, since it can be object too!
|
|
1708
|
+
readOnly: restProps.readOnly ?? false, // intentionally not evaluated as boolean, since it can be object too!
|
|
1709
|
+
"data-testid": dataTestId ?? "select",
|
|
1710
|
+
prefix: restProps.prefix,
|
|
1711
|
+
hasError: restProps.hasError,
|
|
1712
|
+
fieldSize: restProps.fieldSize,
|
|
1713
|
+
getOptionLabelDescription: restProps.getOptionLabelDescription,
|
|
1714
|
+
getOptionPrefix: restProps.getOptionPrefix,
|
|
1715
|
+
isMulti: restProps.isMulti ?? false,
|
|
1716
|
+
className: restProps.className,
|
|
1717
|
+
autoComplete: restProps.autoComplete,
|
|
1427
1718
|
});
|
|
1428
|
-
const
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1719
|
+
const interactable = react.useMemo(() => !Boolean(disabled) && !Boolean(restProps.readOnly), [disabled, restProps.readOnly]);
|
|
1720
|
+
// Determine the portal target for the menu
|
|
1721
|
+
const portalTarget = react.useMemo(() => (restProps.menuPortalTarget !== undefined ? restProps.menuPortalTarget : document.body), [restProps.menuPortalTarget]);
|
|
1722
|
+
// Use custom scroll blocking hook to prevent layout shifts
|
|
1723
|
+
// Pass the portal target so we only block scroll when menu is portaled to document.body
|
|
1724
|
+
const { blockScroll, restoreScroll } = reactComponents.useScrollBlock(portalTarget);
|
|
1725
|
+
// Store callbacks in refs to keep wrapper callbacks stable
|
|
1726
|
+
// This prevents unnecessary re-renders when parent components don't memoize these callbacks
|
|
1727
|
+
const onMenuOpenRef = react.useRef(onMenuOpen);
|
|
1728
|
+
const onMenuCloseRef = react.useRef(onMenuClose);
|
|
1729
|
+
react.useEffect(() => {
|
|
1730
|
+
onMenuOpenRef.current = onMenuOpen;
|
|
1731
|
+
}, [onMenuOpen]);
|
|
1732
|
+
react.useEffect(() => {
|
|
1733
|
+
onMenuCloseRef.current = onMenuClose;
|
|
1734
|
+
}, [onMenuClose]);
|
|
1735
|
+
// Wrap user's onMenuOpen callback to block scrolling
|
|
1736
|
+
// We apply scroll blocking directly instead of using react-select's menuShouldBlockScroll
|
|
1737
|
+
// because it doesn't properly account for existing body padding, causing layout shifts
|
|
1738
|
+
// See commeont next to menuShouldBlockScroll below for more
|
|
1739
|
+
const handleMenuOpen = react.useCallback(() => {
|
|
1740
|
+
blockScroll();
|
|
1741
|
+
onMenuOpenRef.current?.();
|
|
1742
|
+
}, [blockScroll]);
|
|
1743
|
+
// Wrap user's onMenuClose callback to restore scrolling
|
|
1744
|
+
const handleMenuClose = react.useCallback(() => {
|
|
1745
|
+
restoreScroll();
|
|
1746
|
+
onMenuCloseRef.current?.();
|
|
1747
|
+
}, [restoreScroll]);
|
|
1748
|
+
// Store restProps in a ref to keep dependencies stable while always having the latest values
|
|
1749
|
+
// This allows us to access restProps in useMemo without including them in the dependency array
|
|
1750
|
+
// Criteria for props in restProps:
|
|
1751
|
+
// - Props used in computations are already tracked via derived values (interactable, portalTarget, etc.)
|
|
1752
|
+
// - Other props are accessed via ref - they'll always be latest but won't trigger recalculation
|
|
1753
|
+
// This is a trade-off: we prioritize stability over perfect reactivity for less critical props
|
|
1754
|
+
const restPropsRef = react.useRef(restProps);
|
|
1755
|
+
react.useEffect(() => {
|
|
1756
|
+
restPropsRef.current = restProps;
|
|
1757
|
+
}, [restProps]);
|
|
1758
|
+
// eslint-disable-next-line react-hooks/refs
|
|
1759
|
+
return react.useMemo(() => {
|
|
1760
|
+
const currentRestProps = restPropsRef.current;
|
|
1761
|
+
return {
|
|
1762
|
+
...currentRestProps,
|
|
1763
|
+
options,
|
|
1764
|
+
value,
|
|
1765
|
+
onChange,
|
|
1766
|
+
defaultValue,
|
|
1767
|
+
components: customComponents,
|
|
1768
|
+
unstyled: true,
|
|
1769
|
+
"aria-label": currentRestProps.label,
|
|
1770
|
+
"data-testid": dataTestId ?? "select",
|
|
1771
|
+
tabSelectsValue: false,
|
|
1772
|
+
blurInputOnSelect: false,
|
|
1773
|
+
// This configuration allows for more flexible positioning control of the dropdown.
|
|
1774
|
+
// Setting menuPortalTarget to 'null' specifies that the dropdown should be rendered within
|
|
1775
|
+
// the parent element instead of 'document.body'.
|
|
1776
|
+
menuPortalTarget: portalTarget,
|
|
1777
|
+
isSearchable: interactable ? (currentRestProps.isSearchable ?? true) : false,
|
|
1778
|
+
// Disable react-select's built-in scroll blocking as we handle it ourselves in onMenuOpen/onMenuClose
|
|
1779
|
+
// to prevent layout shifts caused by not accounting for existing body padding in the react-select implementation.
|
|
1780
|
+
// See: https://github.com/JedWatson/react-select/issues/5342 AND https://github.com/JedWatson/react-select/issues/5020
|
|
1781
|
+
menuShouldBlockScroll: false,
|
|
1782
|
+
menuShouldScrollIntoView: true,
|
|
1783
|
+
openMenuOnClick: interactable ? (currentRestProps.openMenuOnClick ?? true) : false,
|
|
1784
|
+
openMenuOnFocus: Boolean(currentRestProps.openMenuOnFocus),
|
|
1785
|
+
closeMenuOnSelect: !Boolean(currentRestProps.isMulti),
|
|
1786
|
+
isDisabled: Boolean(disabled),
|
|
1787
|
+
isClearable: Boolean(currentRestProps.isClearable),
|
|
1788
|
+
menuPlacement: currentRestProps.menuPlacement ?? "auto",
|
|
1789
|
+
placeholder: interactable ? currentRestProps.placeholder : undefined,
|
|
1790
|
+
hideSelectedOptions: Boolean(currentRestProps.hideSelectedOptions),
|
|
1791
|
+
menuIsOpen: interactable ? undefined : false, // close it if not interactable, otherwise leave state to react-select
|
|
1792
|
+
// Wire up our custom menu open/close handlers
|
|
1793
|
+
onMenuOpen: handleMenuOpen,
|
|
1794
|
+
onMenuClose: handleMenuClose,
|
|
1795
|
+
// 👇 putting these here to avoid them _accidentally_ being overwritten in the future👇
|
|
1796
|
+
maxMenuHeight: undefined, // controlled custom components styling
|
|
1797
|
+
minMenuHeight: undefined, // controlled custom components styling
|
|
1798
|
+
theme: undefined,
|
|
1799
|
+
classNames: undefined,
|
|
1800
|
+
styles: undefined,
|
|
1801
|
+
};
|
|
1802
|
+
}, [
|
|
1446
1803
|
customComponents,
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1804
|
+
disabled,
|
|
1805
|
+
interactable,
|
|
1806
|
+
handleMenuOpen,
|
|
1807
|
+
handleMenuClose,
|
|
1808
|
+
dataTestId,
|
|
1809
|
+
portalTarget,
|
|
1810
|
+
// Critical props that must trigger recalculation when they change
|
|
1811
|
+
options,
|
|
1812
|
+
value,
|
|
1813
|
+
onChange,
|
|
1814
|
+
defaultValue,
|
|
1815
|
+
// restPropsRef is intentionally not included - we access restPropsRef.current inside useMemo
|
|
1816
|
+
// to always get the latest restProps without causing recalculation when the object reference changes
|
|
1817
|
+
]);
|
|
1451
1818
|
};
|
|
1452
1819
|
|
|
1453
1820
|
// This is here to ensure the bundled react-components can expose the react-select for jest in external iris apps.
|
|
1454
1821
|
const ReactSyncSelect = ReactSelect.default || ReactSelect;
|
|
1455
1822
|
/**
|
|
1456
|
-
*
|
|
1823
|
+
* BaseSelect are input components used to choose a value from a set.
|
|
1457
1824
|
*
|
|
1458
1825
|
* @param {SelectProps} props - The props for the Select component
|
|
1459
1826
|
* @returns {ReactElement} Select component
|
|
1460
1827
|
*/
|
|
1461
1828
|
const BaseSelect = (props) => {
|
|
1462
|
-
const
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
isClearable,
|
|
1490
|
-
id,
|
|
1491
|
-
onMenuScrollToBottom,
|
|
1492
|
-
onInputChange,
|
|
1493
|
-
hideSelectedOptions,
|
|
1494
|
-
isDisabled: Boolean(disabled),
|
|
1495
|
-
}), [
|
|
1496
|
-
classNamePrefix,
|
|
1497
|
-
customComponents,
|
|
1498
|
-
customStyles,
|
|
1499
|
-
dataTestId,
|
|
1500
|
-
disabled,
|
|
1501
|
-
hideSelectedOptions,
|
|
1502
|
-
id,
|
|
1503
|
-
isClearable,
|
|
1504
|
-
isLoading,
|
|
1505
|
-
isMulti,
|
|
1506
|
-
isSearchable,
|
|
1507
|
-
label,
|
|
1508
|
-
maxMenuHeight,
|
|
1509
|
-
menuIsOpen,
|
|
1510
|
-
menuPlacement,
|
|
1511
|
-
onChange,
|
|
1512
|
-
onInputChange,
|
|
1513
|
-
onMenuScrollToBottom,
|
|
1514
|
-
openMenuOnClick,
|
|
1515
|
-
openMenuOnFocus,
|
|
1516
|
-
props.menuPortalTarget,
|
|
1517
|
-
readOnly,
|
|
1518
|
-
value,
|
|
1519
|
-
]);
|
|
1520
|
-
const renderAsDisabled = Boolean(props.disabled) || props.readOnly;
|
|
1521
|
-
return (jsxRuntime.jsxs("div", { className: cvaSelect({
|
|
1522
|
-
invalid: hasError,
|
|
1523
|
-
fieldSize: fieldSize,
|
|
1524
|
-
disabled: renderAsDisabled,
|
|
1525
|
-
className: props.className,
|
|
1526
|
-
}), "data-testid": dataTestId, ref: refContainer, children: [prefix !== undefined ? (jsxRuntime.jsx("div", { className: cvaSelectPrefixSuffix({ kind: "prefix" }), "data-testid": dataTestId ? `${dataTestId}-prefix` : null, children: prefix })) : null, async ? (jsxRuntime.jsx(ReactAsyncSelect, { ...props, ...reactSelectProps, ...async, menuPosition: menuPosition, onMenuClose: closeMenuHandler, onMenuOpen: openMenuHandler, placeholder: renderAsDisabled ? null : props.placeholder })) : (jsxRuntime.jsx(ReactSyncSelect, { ...props, ...reactSelectProps, isMulti: isMulti, menuPosition: menuPosition, onMenuClose: closeMenuHandler, onMenuOpen: openMenuHandler, options: options, placeholder: renderAsDisabled ? null : props.placeholder })), typeof props.disabled === "object" ? (jsxRuntime.jsx("div", { className: cvaSelectPrefixSuffix({ kind: "suffix" }), "data-testid": dataTestId ? `${dataTestId}-locked` : null, children: jsxRuntime.jsx(InputLockReasonTooltip, { ...props.disabled }) })) : null] }));
|
|
1829
|
+
const select = useSelect(props);
|
|
1830
|
+
return props.async ? jsxRuntime.jsx(ReactAsyncSelect, { ...select }) : jsxRuntime.jsx(ReactSyncSelect, { ...select });
|
|
1831
|
+
};
|
|
1832
|
+
|
|
1833
|
+
/**
|
|
1834
|
+
* A hook used by creatable selects that extends useSelect with creatable-specific functionality
|
|
1835
|
+
*
|
|
1836
|
+
* @param props - The props for the CreatableSelect component
|
|
1837
|
+
* @returns {ReactCreatableProps} Props for react-select creatable component
|
|
1838
|
+
*/
|
|
1839
|
+
const useCreatableSelect = (props) => {
|
|
1840
|
+
const { onCreateOption, allowCreateWhileLoading, isMulti } = props;
|
|
1841
|
+
const baseSelectProps = useSelect(props);
|
|
1842
|
+
// Store onCreateOption in a ref to keep wrapper stable
|
|
1843
|
+
// This prevents unnecessary re-renders when parent components don't memoize this callback
|
|
1844
|
+
const onCreateOptionRef = react.useRef(onCreateOption);
|
|
1845
|
+
react.useEffect(() => {
|
|
1846
|
+
onCreateOptionRef.current = onCreateOption;
|
|
1847
|
+
}, [onCreateOption]);
|
|
1848
|
+
return react.useMemo(() => ({
|
|
1849
|
+
...baseSelectProps,
|
|
1850
|
+
allowCreateWhileLoading,
|
|
1851
|
+
onCreateOption: onCreateOptionRef.current,
|
|
1852
|
+
// Override some defaults specific to creatable selects
|
|
1853
|
+
closeMenuOnSelect: false, // Keep menu open for multi-creation
|
|
1854
|
+
blurInputOnSelect: !Boolean(isMulti), // Only blur if not multi
|
|
1855
|
+
}), [baseSelectProps, allowCreateWhileLoading, isMulti]);
|
|
1527
1856
|
};
|
|
1528
|
-
BaseSelect.displayName = "BaseSelect";
|
|
1529
1857
|
|
|
1530
1858
|
/**
|
|
1531
1859
|
* CreatableSelects are input components used to choose a value from a set.
|
|
1532
1860
|
*
|
|
1533
|
-
|
|
1534
|
-
*
|
|
1861
|
+
/**
|
|
1862
|
+
* CreatableSelect is a component that allows users to select from existing options or create new ones.
|
|
1863
|
+
*
|
|
1864
|
+
* @template TOption - The option type.
|
|
1865
|
+
* @template TIsAsync - Indicates whether the component is asynchronous.
|
|
1866
|
+
* @template TIsMulti - Indicates whether multiple selections are allowed.
|
|
1867
|
+
* @template TGroup - The group base type for options.
|
|
1868
|
+
* @param {CreatableSelectProps} props - The props to configure the CreatableSelect component.
|
|
1869
|
+
* @returns {ReactElement} A ReactElement rendering the CreatableSelect.
|
|
1535
1870
|
*/
|
|
1536
1871
|
const CreatableSelect = (props) => {
|
|
1537
|
-
const
|
|
1538
|
-
|
|
1539
|
-
const reactCreatableSelectProps = react.useMemo(() => ({
|
|
1540
|
-
value,
|
|
1541
|
-
menuPlacement,
|
|
1542
|
-
maxMenuHeight,
|
|
1543
|
-
onChange,
|
|
1544
|
-
"aria-label": label,
|
|
1545
|
-
"data-testid": dataTestId,
|
|
1546
|
-
components: customComponents,
|
|
1547
|
-
styles: customStyles,
|
|
1548
|
-
tabSelectsValue: false,
|
|
1549
|
-
blurInputOnSelect: !isMulti,
|
|
1550
|
-
menuPortalTarget: props.menuPortalTarget || document.body,
|
|
1551
|
-
isSearchable: disabled || readOnly ? false : isSearchable,
|
|
1552
|
-
menuShouldBlockScroll: true,
|
|
1553
|
-
menuShouldScrollIntoView: true,
|
|
1554
|
-
openMenuOnFocus,
|
|
1555
|
-
menuIsOpen: !readOnly ? menuIsOpen : false,
|
|
1556
|
-
openMenuOnClick,
|
|
1557
|
-
closeMenuOnSelect: false,
|
|
1558
|
-
isMulti,
|
|
1559
|
-
classNamePrefix,
|
|
1560
|
-
isLoading,
|
|
1561
|
-
isClearable,
|
|
1562
|
-
id,
|
|
1563
|
-
onMenuScrollToBottom,
|
|
1564
|
-
onInputChange,
|
|
1565
|
-
allowCreateWhileLoading,
|
|
1566
|
-
onCreateOption,
|
|
1567
|
-
isDisabled: Boolean(disabled),
|
|
1568
|
-
}), [
|
|
1569
|
-
allowCreateWhileLoading,
|
|
1570
|
-
classNamePrefix,
|
|
1571
|
-
customComponents,
|
|
1572
|
-
customStyles,
|
|
1573
|
-
dataTestId,
|
|
1574
|
-
disabled,
|
|
1575
|
-
id,
|
|
1576
|
-
isClearable,
|
|
1577
|
-
isLoading,
|
|
1578
|
-
isMulti,
|
|
1579
|
-
isSearchable,
|
|
1580
|
-
label,
|
|
1581
|
-
maxMenuHeight,
|
|
1582
|
-
menuIsOpen,
|
|
1583
|
-
menuPlacement,
|
|
1584
|
-
onChange,
|
|
1585
|
-
onCreateOption,
|
|
1586
|
-
onInputChange,
|
|
1587
|
-
onMenuScrollToBottom,
|
|
1588
|
-
openMenuOnClick,
|
|
1589
|
-
openMenuOnFocus,
|
|
1590
|
-
props.menuPortalTarget,
|
|
1591
|
-
readOnly,
|
|
1592
|
-
value,
|
|
1593
|
-
]);
|
|
1594
|
-
const renderAsDisabled = Boolean(props.disabled) || props.readOnly;
|
|
1595
|
-
return (jsxRuntime.jsxs("div", { className: cvaSelect({ invalid: hasError, disabled: renderAsDisabled, className: props.className }), "data-testid": dataTestId, ref: refContainer, children: [prefix !== undefined ? (jsxRuntime.jsx("div", { className: cvaSelectPrefixSuffix({ kind: "prefix" }), "data-testid": dataTestId ? `${dataTestId}-prefix` : null, children: prefix })) : null, async ? (jsxRuntime.jsx(ReactAsyncCreatableSelect, { ...props, ...reactCreatableSelectProps, ...async, onMenuClose: closeMenuHandler, onMenuOpen: openMenuHandler, placeholder: renderAsDisabled ? null : props.placeholder })) : (jsxRuntime.jsx(ReactCreatableSelect, { ...props, ...reactCreatableSelectProps, hideSelectedOptions: false, isMulti: isMulti, onMenuClose: closeMenuHandler, onMenuOpen: openMenuHandler, options: options, placeholder: renderAsDisabled ? null : props.placeholder })), typeof props.disabled === "object" ? (jsxRuntime.jsx("div", { className: cvaSelectPrefixSuffix({ kind: "suffix" }), "data-testid": dataTestId ? `${dataTestId}-locked` : null, children: jsxRuntime.jsx(InputLockReasonTooltip, { ...props.disabled }) })) : null] }));
|
|
1872
|
+
const creatableSelect = useCreatableSelect(props);
|
|
1873
|
+
return props.async ? (jsxRuntime.jsx(ReactAsyncCreatableSelect, { ...creatableSelect })) : (jsxRuntime.jsx(ReactCreatableSelect, { ...creatableSelect }));
|
|
1596
1874
|
};
|
|
1597
|
-
CreatableSelect.displayName = "CreatableSelect";
|
|
1598
1875
|
|
|
1599
1876
|
/**
|
|
1600
1877
|
* The Label component is used for labels for input fields.
|
|
@@ -1637,7 +1914,7 @@ const cvaHelpAddon = cssClassVarianceUtilities.cvaMerge(["ml-auto"]);
|
|
|
1637
1914
|
* @param {FormGroupProps} props - The props for the FormGroup component
|
|
1638
1915
|
* @returns {ReactElement} FormGroup component
|
|
1639
1916
|
*/
|
|
1640
|
-
const FormGroup = ({ isInvalid, isWarning, helpText, helpAddon, tip, className, "data-testid": dataTestId, label, htmlFor, children, required = false, }) => {
|
|
1917
|
+
const FormGroup = ({ isInvalid = false, isWarning = false, helpText, helpAddon, tip, className, "data-testid": dataTestId, label, htmlFor, children, required = false, }) => {
|
|
1641
1918
|
const [t] = useTranslation();
|
|
1642
1919
|
const validationStateIcon = react.useMemo(() => {
|
|
1643
1920
|
const color = isInvalid ? "danger" : isWarning ? "warning" : null;
|
|
@@ -1731,7 +2008,7 @@ const isValidHEXColor = (value) => {
|
|
|
1731
2008
|
* ColorField validates that user enters a valid color address.
|
|
1732
2009
|
*
|
|
1733
2010
|
*/
|
|
1734
|
-
const ColorField = react.forwardRef(({ label, id, tip, helpText, errorMessage, helpAddon, className, defaultValue, "data-testid": dataTestId, value: propValue, onChange, isInvalid, onBlur, fieldSize = "medium", style, disabled, readOnly,
|
|
2011
|
+
const ColorField = react.forwardRef(({ label, id, tip, helpText, errorMessage, helpAddon, className, defaultValue, "data-testid": dataTestId, value: propValue, onChange, isInvalid, onBlur, fieldSize = "medium", style, disabled, readOnly, isWarning, inputClassName, ...inputProps }, ref) => {
|
|
1735
2012
|
const renderAsDisabled = Boolean(disabled);
|
|
1736
2013
|
const renderAsReadonly = Boolean(readOnly);
|
|
1737
2014
|
const htmlForId = react.useMemo(() => (id ? id : "colorField-" + sharedUtils.uuidv4()), [id]);
|
|
@@ -1783,7 +2060,7 @@ const ColorField = react.forwardRef(({ label, id, tip, helpText, errorMessage, h
|
|
|
1783
2060
|
className,
|
|
1784
2061
|
}), "data-testid": dataTestId ? `${dataTestId}-container` : undefined, style: style, children: [jsxRuntime.jsx("input", { "aria-labelledby": htmlForId + "-label", className: cvaInputColorField({ readOnly: renderAsReadonly }), "data-testid": dataTestId, defaultValue: defaultValue, disabled: renderAsDisabled, id: htmlForId, onBlur: handleBlur, onChange: handleInputChange, readOnly: renderAsReadonly, ref: innerRef, type: "color", value: innerValue }), jsxRuntime.jsx("input", { "aria-labelledby": htmlForId + "-label-text", className: cvaInputElement({
|
|
1785
2062
|
className: tailwindMerge.twMerge("px-1 focus-visible:outline-none", inputClassName),
|
|
1786
|
-
}), "data-testid": dataTestId ? `${dataTestId}-textField` : undefined, disabled: renderAsDisabled, onBlur: handleBlur, onChange: handleInputChange, readOnly: renderAsReadonly
|
|
2063
|
+
}), "data-testid": dataTestId ? `${dataTestId}-textField` : undefined, disabled: renderAsDisabled, onBlur: handleBlur, onChange: handleInputChange, readOnly: renderAsReadonly, type: "text", value: innerValue, ...inputProps }), jsxRuntime.jsx(GenericActionsRenderer, { disabled: renderAsDisabled || renderAsReadonly, fieldSize: fieldSize, genericAction: "edit", innerRef: innerRef, tooltipLabel: t("colorField.tooltip") })] }) }));
|
|
1787
2064
|
});
|
|
1788
2065
|
ColorField.displayName = "ColorField";
|
|
1789
2066
|
|
|
@@ -1969,7 +2246,7 @@ const EmailBaseInput = ({ fieldSize = "medium", disabled = false, "data-testid":
|
|
|
1969
2246
|
setEmail(newValue);
|
|
1970
2247
|
}, [onChange]);
|
|
1971
2248
|
const renderAsInvalid = (email && !validateEmailAddress(email)) || isInvalid;
|
|
1972
|
-
return (jsxRuntime.jsx(BaseInput, { actions: email && email.length > 0 ? (jsxRuntime.jsx(ActionButton, { "data-testid": dataTestId ? `${dataTestId}-emailIcon` : undefined, disabled: disableAction || isInvalid, onClick: sendEmail, size: fieldSize
|
|
2249
|
+
return (jsxRuntime.jsx(BaseInput, { actions: email && email.length > 0 ? (jsxRuntime.jsx(ActionButton, { "data-testid": dataTestId ? `${dataTestId}-emailIcon` : undefined, disabled: disableAction || isInvalid, onClick: sendEmail, size: fieldSize, type: "EMAIL", value: email })) : null, "data-testid": dataTestId, disabled: disabled, fieldSize: fieldSize, isInvalid: renderAsInvalid, onChange: handleChange, placeholder: rest.placeholder || "mail@example.com", ref: ref, type: "email", ...rest }));
|
|
1973
2250
|
};
|
|
1974
2251
|
|
|
1975
2252
|
/**
|
|
@@ -2008,14 +2285,17 @@ function isWritableRef(r) {
|
|
|
2008
2285
|
}
|
|
2009
2286
|
/**
|
|
2010
2287
|
* Multi adapter:
|
|
2011
|
-
* - keeps
|
|
2288
|
+
* - keeps TOption[] semantics (via `MultiValue<TOption>`)
|
|
2012
2289
|
* - renders FormGroup chrome (label, help, error)
|
|
2013
2290
|
* - exposes a hidden <select> for a stable ref target
|
|
2014
2291
|
* - optionally renders one hidden <input> per selected option IF `getOptionValue` is provided
|
|
2015
2292
|
* - passes through all remaining BaseSelect props with isMulti=true
|
|
2293
|
+
*
|
|
2294
|
+
* @param {FormFieldSelectAdapterMultiProps} props - The props for the FormFieldSelectAdapterMulti component
|
|
2295
|
+
* @returns {ReactElement} FormFieldSelectAdapterMulti component
|
|
2016
2296
|
*/
|
|
2017
2297
|
const FormFieldSelectAdapterMulti = (props) => {
|
|
2018
|
-
const { className, "data-testid": dataTestId, helpText, helpAddon, tip, label, isInvalid, errorMessage, name, onBlur, options, value, defaultValue, id, onChange, children, ref, ...selectProps } = props;
|
|
2298
|
+
const { className, "data-testid": dataTestId, helpText, helpAddon, tip, label, isInvalid, errorMessage, name, onBlur, options, value, defaultValue, id, htmlFor: htmlForProp, onChange, children, ref, ...selectProps } = props;
|
|
2019
2299
|
// Hidden select for a stable DOM ref target (API parity with single adapter)
|
|
2020
2300
|
const innerRef = react.useRef(null);
|
|
2021
2301
|
// Bridge external ref (supports both callback and object refs)
|
|
@@ -2030,25 +2310,28 @@ const FormFieldSelectAdapterMulti = (props) => {
|
|
|
2030
2310
|
// Determine invalid state
|
|
2031
2311
|
const renderAsInvalid = react.useMemo(() => (isInvalid === undefined ? Boolean(errorMessage) : isInvalid), [errorMessage, isInvalid]);
|
|
2032
2312
|
// id to connect label and control
|
|
2033
|
-
const controlId = react.useMemo(() =>
|
|
2313
|
+
const controlId = react.useMemo(() => htmlForProp ?? id ?? "multiSelectField-" + sharedUtils.uuidv4(), [htmlForProp, id]);
|
|
2034
2314
|
// If consumers provided getOptionValue (from BaseSelect props),
|
|
2035
2315
|
// we can render hidden inputs for native form submit / RHF.
|
|
2036
2316
|
const selectPropsWithAccessors = selectProps;
|
|
2037
|
-
const getOptionValue = typeof selectPropsWithAccessors.getOptionValue === "function"
|
|
2317
|
+
const getOptionValue = react.useMemo(() => typeof selectPropsWithAccessors.getOptionValue === "function"
|
|
2318
|
+
? selectPropsWithAccessors.getOptionValue
|
|
2319
|
+
: undefined, [selectPropsWithAccessors]);
|
|
2038
2320
|
// Compute selected options snapshot for hidden inputs (prefer controlled `value`)
|
|
2039
2321
|
const selectedOptions = react.useMemo(() => value ?? defaultValue ?? [], [value, defaultValue]);
|
|
2040
2322
|
// Build the exact prop bag for BaseSelect (multi=true).
|
|
2041
|
-
const childProps = {
|
|
2323
|
+
const childProps = react.useMemo(() => ({
|
|
2042
2324
|
...selectProps,
|
|
2325
|
+
"data-testid": dataTestId,
|
|
2043
2326
|
id: controlId,
|
|
2044
2327
|
onBlur,
|
|
2045
2328
|
options,
|
|
2046
2329
|
isMulti: true,
|
|
2047
2330
|
value: value ?? null,
|
|
2048
2331
|
defaultValue,
|
|
2049
|
-
onChange
|
|
2050
|
-
};
|
|
2051
|
-
return (jsxRuntime.jsxs(FormGroup, { className: className, "data-testid": dataTestId, helpAddon: helpAddon, helpText: (renderAsInvalid && errorMessage) || helpText, htmlFor: controlId, isInvalid: renderAsInvalid, label: label, required: "required" in selectProps && selectProps.required
|
|
2332
|
+
onChange,
|
|
2333
|
+
}), [selectProps, dataTestId, controlId, onBlur, options, value, defaultValue, onChange]);
|
|
2334
|
+
return (jsxRuntime.jsxs(FormGroup, { className: className, "data-testid": dataTestId ? `${dataTestId}-FormGroup` : undefined, helpAddon: helpAddon, helpText: (renderAsInvalid && errorMessage) || helpText, htmlFor: controlId, isInvalid: renderAsInvalid, label: label, required: "required" in selectProps && selectProps.required
|
|
2052
2335
|
? !(("disabled" in selectProps && Boolean(selectProps.disabled)) ||
|
|
2053
2336
|
("readOnly" in selectProps && Boolean(selectProps.readOnly)))
|
|
2054
2337
|
: false, tip: tip, children: [jsxRuntime.jsx("select", { "aria-hidden": "true", defaultValue: "", hidden: true, name: name, ref: innerRef }), typeof getOptionValue === "function" &&
|
|
@@ -2057,12 +2340,13 @@ const FormFieldSelectAdapterMulti = (props) => {
|
|
|
2057
2340
|
return typeof primitiveValue === "string" ? (jsxRuntime.jsx("input", { name: name, type: "hidden", value: primitiveValue }, `${primitiveValue}-${idx}`)) : null;
|
|
2058
2341
|
}), children(childProps)] }));
|
|
2059
2342
|
};
|
|
2060
|
-
FormFieldSelectAdapterMulti.displayName = "FormFieldSelectAdapterMulti";
|
|
2061
2343
|
|
|
2062
2344
|
/**
|
|
2063
|
-
* MultiSelectField
|
|
2064
|
-
*
|
|
2065
|
-
*
|
|
2345
|
+
* MultiSelectField is a custom Select component wrapped in the FormGroup component
|
|
2346
|
+
* that allows you to select multiple options from a list.
|
|
2347
|
+
*
|
|
2348
|
+
* @param {MultiSelectFieldProps} props - The props for the MultiSelectField component
|
|
2349
|
+
* @returns {ReactElement} MultiSelectField component
|
|
2066
2350
|
*/
|
|
2067
2351
|
const MultiSelectField = ({ ref, ...props }) => {
|
|
2068
2352
|
return (jsxRuntime.jsx(FormFieldSelectAdapterMulti, { ...props, ref: ref, children: convertedProps => jsxRuntime.jsx(BaseSelect, { ...convertedProps }) }));
|
|
@@ -2772,15 +3056,16 @@ Search.displayName = "Search";
|
|
|
2772
3056
|
/**
|
|
2773
3057
|
*
|
|
2774
3058
|
*/
|
|
2775
|
-
const FormFieldSelectAdapter = ({ className, "data-testid": dataTestId, helpText, helpAddon, tip, label, isInvalid, errorMessage, name, onBlur, options, value, defaultValue, id, onChange, children, ref, ...rest }) => {
|
|
3059
|
+
const FormFieldSelectAdapter = ({ className, "data-testid": dataTestId, helpText, helpAddon, tip, label, isInvalid = false, errorMessage, name, onBlur, options, value, defaultValue, id, htmlFor: htmlForProp, onChange, children, ref, required = false, ...rest }) => {
|
|
2776
3060
|
const isFirstRender = reactComponents.useIsFirstRender();
|
|
2777
|
-
const [innerValue, setInnerValue] = react.useState(value
|
|
3061
|
+
const [innerValue, setInnerValue] = react.useState(value ?? defaultValue);
|
|
2778
3062
|
react.useEffect(() => {
|
|
2779
3063
|
setInnerValue(defaultValue);
|
|
2780
3064
|
}, [defaultValue]);
|
|
2781
|
-
const renderAsInvalid = isInvalid
|
|
2782
|
-
const htmlFor = react.useMemo(() =>
|
|
3065
|
+
const renderAsInvalid = isInvalid || Boolean(errorMessage);
|
|
3066
|
+
const htmlFor = react.useMemo(() => htmlForProp ?? id ?? "selectField-" + sharedUtils.uuidv4(), [htmlForProp, id]);
|
|
2783
3067
|
const innerRef = react.useRef(null);
|
|
3068
|
+
// eslint-disable-next-line local-rules/no-typescript-assertion, @typescript-eslint/no-non-null-assertion
|
|
2784
3069
|
react.useImperativeHandle(ref, () => innerRef.current, []);
|
|
2785
3070
|
react.useEffect(() => {
|
|
2786
3071
|
if (innerValue === undefined) {
|
|
@@ -2796,8 +3081,13 @@ const FormFieldSelectAdapter = ({ className, "data-testid": dataTestId, helpText
|
|
|
2796
3081
|
const optionsWithCurrentSelectionBackupOption = [
|
|
2797
3082
|
// Add the current selection in case there's no options loaded yet (in CreatableSelect)
|
|
2798
3083
|
// Also _don't_ add it if it's a duplicate
|
|
2799
|
-
|
|
2800
|
-
|
|
3084
|
+
// Only add backup option when innerValue is defined (not undefined) and not an empty string
|
|
3085
|
+
// Note: Empty string is used as a sentinel value for "no selection" (see onChange handler below)
|
|
3086
|
+
// and should not be added as a backup option. Zero (0) is a valid option value, so we allow it.
|
|
3087
|
+
innerValue !== undefined && innerValue !== "" && !options.find(option => option.value === innerValue)
|
|
3088
|
+
? // It is safe enough to assert this because the only properties that are used are value and label, and those are present in the TOption type.
|
|
3089
|
+
// eslint-disable-next-line local-rules/no-typescript-assertion
|
|
3090
|
+
{ value: innerValue, label: String(innerValue) }
|
|
2801
3091
|
: null,
|
|
2802
3092
|
...options,
|
|
2803
3093
|
].filter(sharedUtils.nonNullable);
|
|
@@ -2805,15 +3095,16 @@ const FormFieldSelectAdapter = ({ className, "data-testid": dataTestId, helpText
|
|
|
2805
3095
|
return (jsxRuntime.jsxs(FormGroup, { isInvalid: renderAsInvalid,
|
|
2806
3096
|
htmlFor,
|
|
2807
3097
|
className,
|
|
2808
|
-
"data-testid": dataTestId,
|
|
3098
|
+
"data-testid": dataTestId ? `${dataTestId}-FormGroup` : undefined,
|
|
2809
3099
|
helpText: (renderAsInvalid && errorMessage) || helpText,
|
|
2810
3100
|
helpAddon,
|
|
2811
3101
|
tip,
|
|
2812
3102
|
label,
|
|
2813
|
-
required:
|
|
3103
|
+
required: required ? !Boolean(rest.disabled ?? rest.readOnly) : false, children: [jsxRuntime.jsx("select", { onChange, ref: innerRef, name, value: innerValue, hidden: true, children: optionsWithCurrentSelectionBackupOption.map(option => {
|
|
2814
3104
|
return (jsxRuntime.jsx("option", { value: option.value, children: option.label }, option.value));
|
|
2815
3105
|
}) }), children({
|
|
2816
3106
|
...rest,
|
|
3107
|
+
required,
|
|
2817
3108
|
id,
|
|
2818
3109
|
onBlur,
|
|
2819
3110
|
options: optionsWithCurrentSelectionBackupOption,
|
|
@@ -2823,6 +3114,7 @@ const FormFieldSelectAdapter = ({ className, "data-testid": dataTestId, helpText
|
|
|
2823
3114
|
// So even if react-select sends a null value, we need to convert it to an empty string
|
|
2824
3115
|
setInnerValue(!e ? "" : e.value);
|
|
2825
3116
|
},
|
|
3117
|
+
"data-testid": dataTestId,
|
|
2826
3118
|
value: selectedOption,
|
|
2827
3119
|
defaultValue: selectedOption,
|
|
2828
3120
|
})] }));
|
|
@@ -2838,24 +3130,23 @@ FormFieldSelectAdapter.displayName = "FormFieldSelectAdapter";
|
|
|
2838
3130
|
*
|
|
2839
3131
|
* @param {SelectFieldProps & CreatableSelectProps} props - The props for the CreatableSelectField component
|
|
2840
3132
|
*/
|
|
2841
|
-
const CreatableSelectField = ({ allowCreateWhileLoading, onCreateOption, ref, ...props }) => {
|
|
2842
|
-
const creatableSelectOnlyProps = {
|
|
3133
|
+
const CreatableSelectField = ({ allowCreateWhileLoading = false, onCreateOption, ref, ...props }) => {
|
|
3134
|
+
const creatableSelectOnlyProps = {
|
|
3135
|
+
allowCreateWhileLoading,
|
|
3136
|
+
onCreateOption,
|
|
3137
|
+
};
|
|
2843
3138
|
return (jsxRuntime.jsx(FormFieldSelectAdapter, { ...props, ref: ref, children: convertedProps => jsxRuntime.jsx(CreatableSelect, { ...convertedProps, ...creatableSelectOnlyProps }) }));
|
|
2844
3139
|
};
|
|
2845
3140
|
CreatableSelectField.displayName = "CreatableSelectField";
|
|
2846
3141
|
|
|
2847
3142
|
/**
|
|
2848
|
-
*
|
|
2849
|
-
*
|
|
2850
|
-
* This means that it can easily be added to any form alongside other Field components.
|
|
2851
|
-
*
|
|
2852
|
-
* This is done to make the field compatible with the React-hook-form library.
|
|
3143
|
+
* MultiSelect is a custom Select component wrapped in the FormGroup component
|
|
2853
3144
|
*
|
|
2854
3145
|
* @param {SelectFieldProps} props - The props for the SelectField component
|
|
2855
3146
|
*/
|
|
2856
|
-
|
|
3147
|
+
function SelectField({ ref, ...props }) {
|
|
2857
3148
|
return (jsxRuntime.jsx(FormFieldSelectAdapter, { ...props, ref: ref, children: convertedProps => jsxRuntime.jsx(BaseSelect, { ...convertedProps }) }));
|
|
2858
|
-
}
|
|
3149
|
+
}
|
|
2859
3150
|
SelectField.displayName = "SelectField";
|
|
2860
3151
|
|
|
2861
3152
|
/**
|
|
@@ -3078,7 +3369,7 @@ const cvaUploadInputField = cssClassVarianceUtilities.cvaMerge([
|
|
|
3078
3369
|
*
|
|
3079
3370
|
* NOTE: If shown with a label, please use the `UploadField` component instead.
|
|
3080
3371
|
*/
|
|
3081
|
-
const UploadInput = ({ disabled, acceptedTypes, nonInteractive, uploadLabel, multipleFiles, ref, ...rest }) => (jsxRuntime.jsx("label", { className: "tu-upload-input", children: jsxRuntime.jsx(BaseInput, { accept: acceptedTypes, addonBefore: uploadLabel, disabled: disabled, inputClassName: cvaUploadInputField(), multiple: multipleFiles,
|
|
3372
|
+
const UploadInput = ({ disabled, acceptedTypes, nonInteractive, uploadLabel, multipleFiles, ref, ...rest }) => (jsxRuntime.jsx("label", { className: "tu-upload-input", children: jsxRuntime.jsx(BaseInput, { accept: acceptedTypes, addonBefore: uploadLabel, disabled: disabled, inputClassName: cvaUploadInputField(), multiple: multipleFiles, onClick: event => {
|
|
3082
3373
|
// onClick used to work with nonInteractive option
|
|
3083
3374
|
if (nonInteractive) {
|
|
3084
3375
|
event.preventDefault();
|
|
@@ -3133,10 +3424,10 @@ const validateUrl = (url, required) => {
|
|
|
3133
3424
|
*
|
|
3134
3425
|
* NOTE: If shown with a label, please use the `UrlField` component instead.
|
|
3135
3426
|
*/
|
|
3136
|
-
const UrlBaseInput = ({ "data-testid": dataTestId,
|
|
3427
|
+
const UrlBaseInput = ({ isInvalid = false, "data-testid": dataTestId, disabled = false, fieldSize = "medium", disableAction = false, value, defaultValue, ref, ...rest }) => {
|
|
3137
3428
|
const [url, setUrl] = react.useState(value?.toString() || defaultValue?.toString());
|
|
3138
3429
|
const renderAsInvalid = (url && typeof url === "string" && !validateUrlAddress(url)) || isInvalid;
|
|
3139
|
-
return (jsxRuntime.jsx(BaseInput, { "data-testid": dataTestId ? `${dataTestId}-url-input` : undefined, disabled: disabled, id: "url-input", isInvalid: renderAsInvalid, onChange: e => setUrl(e.target.value), placeholder: rest.placeholder || "https://www.example.com", ref: ref, type: "url", value: url, ...rest, actions: !disableAction && (jsxRuntime.jsx(ActionButton, { "data-testid": (dataTestId && `${dataTestId}-url-input-Icon`) || "url-input-action-icon", disabled: renderAsInvalid || Boolean(disabled) || disableAction, size: fieldSize
|
|
3430
|
+
return (jsxRuntime.jsx(BaseInput, { "data-testid": dataTestId ? `${dataTestId}-url-input` : undefined, disabled: disabled, id: "url-input", isInvalid: renderAsInvalid, onChange: e => setUrl(e.target.value), placeholder: rest.placeholder || "https://www.example.com", ref: ref, type: "url", value: url, ...rest, actions: !disableAction && (jsxRuntime.jsx(ActionButton, { "data-testid": (dataTestId && `${dataTestId}-url-input-Icon`) || "url-input-action-icon", disabled: renderAsInvalid || Boolean(disabled) || disableAction, size: fieldSize, type: "WEB_ADDRESS", value: url })) }));
|
|
3140
3431
|
};
|
|
3141
3432
|
|
|
3142
3433
|
/**
|
|
@@ -3295,7 +3586,6 @@ exports.FormFieldSelectAdapter = FormFieldSelectAdapter;
|
|
|
3295
3586
|
exports.FormGroup = FormGroup;
|
|
3296
3587
|
exports.Label = Label;
|
|
3297
3588
|
exports.MultiSelectField = MultiSelectField;
|
|
3298
|
-
exports.MultiSelectMenuItem = MultiSelectMenuItem;
|
|
3299
3589
|
exports.NumberBaseInput = NumberBaseInput;
|
|
3300
3590
|
exports.NumberField = NumberField;
|
|
3301
3591
|
exports.OptionCard = OptionCard;
|
|
@@ -3309,7 +3599,6 @@ exports.RadioItem = RadioItem;
|
|
|
3309
3599
|
exports.Schedule = Schedule;
|
|
3310
3600
|
exports.Search = Search;
|
|
3311
3601
|
exports.SelectField = SelectField;
|
|
3312
|
-
exports.SingleSelectMenuItem = SingleSelectMenuItem;
|
|
3313
3602
|
exports.TextAreaBaseInput = TextAreaBaseInput;
|
|
3314
3603
|
exports.TextAreaField = TextAreaField;
|
|
3315
3604
|
exports.TextBaseInput = TextBaseInput;
|
|
@@ -3338,15 +3627,21 @@ exports.cvaInputItemPlacementManager = cvaInputItemPlacementManager;
|
|
|
3338
3627
|
exports.cvaInputPrefix = cvaInputPrefix;
|
|
3339
3628
|
exports.cvaInputSuffix = cvaInputSuffix;
|
|
3340
3629
|
exports.cvaLabel = cvaLabel;
|
|
3341
|
-
exports.
|
|
3630
|
+
exports.cvaSelectClearIndicator = cvaSelectClearIndicator;
|
|
3631
|
+
exports.cvaSelectContainer = cvaSelectContainer;
|
|
3342
3632
|
exports.cvaSelectControl = cvaSelectControl;
|
|
3343
|
-
exports.
|
|
3344
|
-
exports.
|
|
3345
|
-
exports.
|
|
3633
|
+
exports.cvaSelectDropdownIconContainer = cvaSelectDropdownIconContainer;
|
|
3634
|
+
exports.cvaSelectDropdownIndicator = cvaSelectDropdownIndicator;
|
|
3635
|
+
exports.cvaSelectIndicatorsContainer = cvaSelectIndicatorsContainer;
|
|
3636
|
+
exports.cvaSelectLoadingMessage = cvaSelectLoadingMessage;
|
|
3346
3637
|
exports.cvaSelectMenu = cvaSelectMenu;
|
|
3347
3638
|
exports.cvaSelectMenuList = cvaSelectMenuList;
|
|
3639
|
+
exports.cvaSelectMultiValue = cvaSelectMultiValue;
|
|
3640
|
+
exports.cvaSelectNoOptionsMessage = cvaSelectNoOptionsMessage;
|
|
3641
|
+
exports.cvaSelectPlaceholder = cvaSelectPlaceholder;
|
|
3348
3642
|
exports.cvaSelectPrefixSuffix = cvaSelectPrefixSuffix;
|
|
3349
|
-
exports.
|
|
3643
|
+
exports.cvaSelectSingleValue = cvaSelectSingleValue;
|
|
3644
|
+
exports.cvaSelectValueContainer = cvaSelectValueContainer;
|
|
3350
3645
|
exports.getCountryAbbreviation = getCountryAbbreviation;
|
|
3351
3646
|
exports.getPhoneNumberWithPlus = getPhoneNumberWithPlus;
|
|
3352
3647
|
exports.isInvalidCountryCode = isInvalidCountryCode;
|
|
@@ -3355,9 +3650,11 @@ exports.isValidHEXColor = isValidHEXColor;
|
|
|
3355
3650
|
exports.parseSchedule = parseSchedule;
|
|
3356
3651
|
exports.phoneErrorMessage = phoneErrorMessage;
|
|
3357
3652
|
exports.serializeSchedule = serializeSchedule;
|
|
3653
|
+
exports.useCreatableSelect = useCreatableSelect;
|
|
3358
3654
|
exports.useCustomComponents = useCustomComponents;
|
|
3359
3655
|
exports.useGetPhoneValidationRules = useGetPhoneValidationRules;
|
|
3360
3656
|
exports.usePhoneInput = usePhoneInput;
|
|
3657
|
+
exports.useSelect = useSelect;
|
|
3361
3658
|
exports.useZodValidators = useZodValidators;
|
|
3362
3659
|
exports.validateEmailAddress = validateEmailAddress;
|
|
3363
3660
|
exports.validatePhoneNumber = validatePhoneNumber;
|