@homebound/beam 2.97.0 → 2.97.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/CssReset.js +29 -2
- package/dist/components/Filters/SingleFilter.js +5 -6
- package/dist/forms/BoundTextField.js +6 -2
- package/dist/inputs/DateField.mock.js +1 -1
- package/dist/inputs/SelectField.d.ts +1 -7
- package/dist/inputs/SelectField.js +3 -4
- package/dist/inputs/SelectField.mock.d.ts +1 -1
- package/dist/inputs/SelectField.mock.js +13 -2
- package/dist/inputs/TextField.d.ts +1 -0
- package/dist/inputs/TextField.js +3 -4
- package/dist/inputs/internal/ListBox.d.ts +1 -0
- package/dist/inputs/internal/ListBox.js +2 -2
- package/dist/inputs/internal/LoadingDots.d.ts +3 -0
- package/dist/inputs/internal/LoadingDots.js +29 -0
- package/dist/inputs/internal/SelectFieldBase.d.ts +20 -16
- package/dist/inputs/internal/SelectFieldBase.js +131 -70
- package/dist/inputs/internal/SelectFieldInput.d.ts +3 -3
- package/dist/inputs/internal/SelectFieldInput.js +17 -18
- package/dist/inputs/internal/VirtualizedOptions.d.ts +1 -0
- package/dist/inputs/internal/VirtualizedOptions.js +7 -2
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +5 -1
- package/dist/utils/sb.js +1 -1
- package/package.json +9 -6
|
@@ -16,8 +16,35 @@ exports.CssReset = CssReset;
|
|
|
16
16
|
// The is primarily for navigation, like breadcrumb links or tab links.
|
|
17
17
|
exports.navLink = "navLink";
|
|
18
18
|
const ourReset = (0, react_1.css) `
|
|
19
|
-
a:not(.${exports.navLink}) {
|
|
20
|
-
|
|
19
|
+
a:not(.${exports.navLink}) {
|
|
20
|
+
color: ${Css_1.Palette.LightBlue700};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
a:visited:not(.${exports.navLink}) {
|
|
24
|
+
color: ${Css_1.Palette.LightBlue500};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Beam animations
|
|
29
|
+
*/
|
|
30
|
+
@keyframes loadingDots {
|
|
31
|
+
0% {
|
|
32
|
+
background-color: ${Css_1.Palette.Gray600};
|
|
33
|
+
}
|
|
34
|
+
50%,
|
|
35
|
+
100% {
|
|
36
|
+
background-color: ${Css_1.Palette.Gray300};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
@keyframes loadingDotsContrast {
|
|
40
|
+
0% {
|
|
41
|
+
background-color: ${Css_1.Palette.Gray200};
|
|
42
|
+
}
|
|
43
|
+
50%,
|
|
44
|
+
100% {
|
|
45
|
+
background-color: ${Css_1.Palette.Gray500};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
21
48
|
`;
|
|
22
49
|
// Copy/pasted from TW which uses this as their base reset.
|
|
23
50
|
const modernNormalizeReset = (0, react_1.css) `
|
|
@@ -12,11 +12,10 @@ exports.singleFilter = singleFilter;
|
|
|
12
12
|
const allOption = {};
|
|
13
13
|
class SingleFilter extends BaseFilter_1.BaseFilter {
|
|
14
14
|
render(value, setValue, tid, inModal, vertical) {
|
|
15
|
-
const { label, defaultValue, options, getOptionLabel, getOptionValue, ...props } = this.props;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
], getOptionValue: (o) => (o === allOption ? undefined : getOptionValue(o)), getOptionLabel: (o) => (o === allOption ? "All" : getOptionLabel(o)), compact: !vertical, value: value, label: this.label, inlineLabel: !inModal && !vertical, hideLabel: inModal, sizeToContent: !inModal && !vertical, nothingSelectedText: "All", onSelect: (value) => setValue(value || undefined) }, this.testId(tid)), void 0));
|
|
15
|
+
const { label, defaultValue, options: maybeOptions, getOptionLabel, getOptionValue, ...props } = this.props;
|
|
16
|
+
const options = Array.isArray(maybeOptions)
|
|
17
|
+
? [allOption, ...maybeOptions]
|
|
18
|
+
: { ...maybeOptions, initial: [allOption, ...maybeOptions.initial] };
|
|
19
|
+
return ((0, jsx_runtime_1.jsx)(SelectField_1.SelectField, Object.assign({}, props, { options: options, getOptionValue: (o) => (o === allOption ? undefined : getOptionValue(o)), getOptionLabel: (o) => (o === allOption ? "All" : getOptionLabel(o)), compact: !vertical, value: value, label: this.label, inlineLabel: !inModal && !vertical, hideLabel: inModal, sizeToContent: !inModal && !vertical, nothingSelectedText: "All", onSelect: (value) => setValue(value || undefined) }, this.testId(tid)), void 0));
|
|
21
20
|
}
|
|
22
21
|
}
|
|
@@ -8,8 +8,12 @@ const utils_1 = require("../utils");
|
|
|
8
8
|
const defaultLabel_1 = require("../utils/defaultLabel");
|
|
9
9
|
/** Wraps `TextField` and binds it to a form field. */
|
|
10
10
|
function BoundTextField(props) {
|
|
11
|
-
const { field, readOnly, onChange = (value) => field.set(value), label = (0, defaultLabel_1.defaultLabel)(field.key), ...others } = props;
|
|
11
|
+
const { field, readOnly, onChange = (value) => field.set(value), label = (0, defaultLabel_1.defaultLabel)(field.key), onEnter, ...others } = props;
|
|
12
12
|
const testId = (0, utils_1.useTestIds)(props, field.key);
|
|
13
|
-
return ((0, jsx_runtime_1.jsx)(mobx_react_1.Observer, { children: () => ((0, jsx_runtime_1.jsx)(inputs_1.TextField, Object.assign({ label: label, value: field.value || undefined, onChange: onChange, readOnly: readOnly !== null && readOnly !== void 0 ? readOnly : field.readOnly, errorMsg: field.touched ? field.errors.join(" ") : undefined, required: field.required, onBlur: () => field.blur(), onFocus: () => field.focus()
|
|
13
|
+
return ((0, jsx_runtime_1.jsx)(mobx_react_1.Observer, { children: () => ((0, jsx_runtime_1.jsx)(inputs_1.TextField, Object.assign({ label: label, value: field.value || undefined, onChange: onChange, readOnly: readOnly !== null && readOnly !== void 0 ? readOnly : field.readOnly, errorMsg: field.touched ? field.errors.join(" ") : undefined, required: field.required, onBlur: () => field.blur(), onFocus: () => field.focus(), onEnter: () => {
|
|
14
|
+
(0, utils_1.maybeCall)(onEnter);
|
|
15
|
+
// Blur the field when the user hits the enter key - as if they are "committing" the value and done with the field
|
|
16
|
+
field.blur();
|
|
17
|
+
} }, testId, others), void 0)) }, void 0));
|
|
14
18
|
}
|
|
15
19
|
exports.BoundTextField = BoundTextField;
|
|
@@ -14,6 +14,6 @@ function DateField(props) {
|
|
|
14
14
|
const { value } = e.target;
|
|
15
15
|
setValue(value);
|
|
16
16
|
onChange((0, date_fns_1.parse)(value, "MM/dd/yy", new Date()));
|
|
17
|
-
}, onBlur: () => (0, utils_1.maybeCall)(onBlur), onFocus: () => (0, utils_1.maybeCall)(onFocus), disabled: !!props.disabled, readOnly: props.readOnly }), void 0));
|
|
17
|
+
}, onBlur: () => (0, utils_1.maybeCall)(onBlur), onFocus: () => (0, utils_1.maybeCall)(onFocus), disabled: !!props.disabled, readOnly: props.readOnly, "data-disabled-days": JSON.stringify(props.disabledDays) }), void 0));
|
|
18
18
|
}
|
|
19
19
|
exports.DateField = DateField;
|
|
@@ -1,16 +1,10 @@
|
|
|
1
|
-
import { ReactNode } from "react";
|
|
2
1
|
import { Value } from "./";
|
|
3
2
|
import { BeamSelectFieldBaseProps } from "./internal/SelectFieldBase";
|
|
4
3
|
import { HasIdAndName, Optional } from "../types";
|
|
5
|
-
export interface SelectFieldProps<O, V extends Value> extends BeamSelectFieldBaseProps<O, V> {
|
|
6
|
-
/** Renders `opt` in the dropdown menu, defaults to the `getOptionLabel` prop. */
|
|
7
|
-
getOptionMenuLabel?: (opt: O) => string | ReactNode;
|
|
8
|
-
getOptionValue: (opt: O) => V;
|
|
9
|
-
getOptionLabel: (opt: O) => string;
|
|
4
|
+
export interface SelectFieldProps<O, V extends Value> extends Omit<BeamSelectFieldBaseProps<O, V>, "values" | "onSelect"> {
|
|
10
5
|
/** The current value; it can be `undefined`, even if `V` cannot be. */
|
|
11
6
|
value: V | undefined;
|
|
12
7
|
onSelect: (value: V, opt: O) => void;
|
|
13
|
-
options: O[];
|
|
14
8
|
}
|
|
15
9
|
/**
|
|
16
10
|
* Provides a non-native select/dropdown widget.
|
|
@@ -7,10 +7,9 @@ function SelectField(props) {
|
|
|
7
7
|
const { getOptionValue = (opt) => opt.id, // if unset, assume O implements HasId
|
|
8
8
|
getOptionLabel = (opt) => opt.name, // if unset, assume O implements HasName
|
|
9
9
|
options, onSelect, value, ...otherProps } = props;
|
|
10
|
-
return ((0, jsx_runtime_1.jsx)(SelectFieldBase_1.SelectFieldBase, Object.assign({}, otherProps, { options: options, getOptionLabel: getOptionLabel, getOptionValue: getOptionValue, values: [value], onSelect: (values) => {
|
|
11
|
-
if (values.length > 0) {
|
|
12
|
-
|
|
13
|
-
onSelect && selectedOption && onSelect(getOptionValue(selectedOption), selectedOption);
|
|
10
|
+
return ((0, jsx_runtime_1.jsx)(SelectFieldBase_1.SelectFieldBase, Object.assign({}, otherProps, { options: options, getOptionLabel: getOptionLabel, getOptionValue: getOptionValue, values: [value], onSelect: (values, options) => {
|
|
11
|
+
if (values.length > 0 && options.length > 0) {
|
|
12
|
+
onSelect && onSelect(values[0], options[0]);
|
|
14
13
|
}
|
|
15
14
|
} }), void 0));
|
|
16
15
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { Key } from "react";
|
|
2
2
|
import { SelectFieldProps } from "./";
|
|
3
3
|
/** Mocks out `SelectField` as a `<select>` field. */
|
|
4
|
-
export declare function SelectField<
|
|
4
|
+
export declare function SelectField<O extends object, V extends Key>(props: SelectFieldProps<O, V>): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
@@ -2,20 +2,31 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SelectField = void 0;
|
|
4
4
|
const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
5
6
|
const utils_1 = require("../utils");
|
|
6
7
|
/** Mocks out `SelectField` as a `<select>` field. */
|
|
7
8
|
function SelectField(props) {
|
|
8
9
|
const { getOptionValue = (o) => o.id, // if unset, assume O implements HasId
|
|
9
10
|
getOptionLabel = (o) => o.name, // if unset, assume O implements HasName
|
|
10
|
-
value, options, onSelect, readOnly = false, errorMsg, onBlur, onFocus, disabled, disabledOptions = [], } = props;
|
|
11
|
+
value, options: maybeOptions, onSelect, readOnly = false, errorMsg, onBlur, onFocus, disabled, disabledOptions = [], } = props;
|
|
11
12
|
const tid = (0, utils_1.useTestIds)(props, "select");
|
|
13
|
+
const [options, setOptions] = (0, react_1.useState)(Array.isArray(maybeOptions) ? maybeOptions : maybeOptions.initial);
|
|
12
14
|
const currentOption = options.find((o) => getOptionValue(o) === value) || options[0];
|
|
15
|
+
(0, react_1.useEffect)(() => {
|
|
16
|
+
if (Array.isArray(maybeOptions) && maybeOptions !== options) {
|
|
17
|
+
setOptions(maybeOptions);
|
|
18
|
+
}
|
|
19
|
+
}, [maybeOptions]);
|
|
13
20
|
return ((0, jsx_runtime_1.jsxs)("select", Object.assign({}, tid, { value:
|
|
14
21
|
// @ts-ignore - allow `value` to be seen as a string
|
|
15
22
|
value !== undefined && value !== "" && currentOption ? getOptionValue(currentOption) : "", onChange: (e) => {
|
|
16
23
|
const option = options.find((o) => `${getOptionValue(o)}` === e.target.value) || options[0];
|
|
17
24
|
onSelect(getOptionValue(option), option);
|
|
18
|
-
}, onFocus: () => {
|
|
25
|
+
}, onFocus: async () => {
|
|
26
|
+
if (!Array.isArray(maybeOptions)) {
|
|
27
|
+
const result = await maybeOptions.load();
|
|
28
|
+
setOptions(result.options);
|
|
29
|
+
}
|
|
19
30
|
if (!readOnly && onFocus)
|
|
20
31
|
onFocus();
|
|
21
32
|
}, onBlur: () => {
|
|
@@ -7,6 +7,7 @@ export interface TextFieldProps<X> extends BeamTextFieldProps<X> {
|
|
|
7
7
|
inlineLabel?: boolean;
|
|
8
8
|
clearable?: boolean;
|
|
9
9
|
api?: MutableRefObject<TextFieldApi | undefined>;
|
|
10
|
+
onEnter?: Callback;
|
|
10
11
|
}
|
|
11
12
|
export declare function TextField<X extends Only<TextFieldXss, X>>(props: TextFieldProps<X>): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
12
13
|
export declare type TextFieldApi = {
|
package/dist/inputs/TextField.js
CHANGED
|
@@ -5,8 +5,9 @@ const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
|
|
|
5
5
|
const react_1 = require("react");
|
|
6
6
|
const react_aria_1 = require("react-aria");
|
|
7
7
|
const TextFieldBase_1 = require("./TextFieldBase");
|
|
8
|
+
const utils_1 = require("../utils");
|
|
8
9
|
function TextField(props) {
|
|
9
|
-
const { disabled: isDisabled = false, readOnly = false, required, errorMsg, value = "", onBlur, onFocus, api, ...otherProps } = props;
|
|
10
|
+
const { disabled: isDisabled = false, readOnly = false, required, errorMsg, value = "", onBlur, onFocus, api, onEnter, ...otherProps } = props;
|
|
10
11
|
const textFieldProps = {
|
|
11
12
|
...otherProps,
|
|
12
13
|
isDisabled,
|
|
@@ -19,10 +20,8 @@ function TextField(props) {
|
|
|
19
20
|
const { labelProps, inputProps } = (0, react_aria_1.useTextField)({
|
|
20
21
|
...textFieldProps,
|
|
21
22
|
onKeyDown: (e) => {
|
|
22
|
-
var _a;
|
|
23
23
|
if (e.key === "Enter") {
|
|
24
|
-
|
|
25
|
-
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.blur();
|
|
24
|
+
(0, utils_1.maybeCall)(onEnter);
|
|
26
25
|
}
|
|
27
26
|
},
|
|
28
27
|
}, inputRef);
|
|
@@ -8,6 +8,7 @@ interface ListBoxProps<O, V extends Key> {
|
|
|
8
8
|
getOptionValue: (opt: O) => V;
|
|
9
9
|
contrast?: boolean;
|
|
10
10
|
positionProps: React.HTMLAttributes<Element>;
|
|
11
|
+
loading?: boolean | (() => JSX.Element);
|
|
11
12
|
}
|
|
12
13
|
/** A ListBox is an internal component used by SelectField and MultiSelectField to display the list of options */
|
|
13
14
|
export declare function ListBox<O, V extends Key>(props: ListBoxProps<O, V>): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
@@ -12,7 +12,7 @@ const VirtualizedOptions_1 = require("./VirtualizedOptions");
|
|
|
12
12
|
/** A ListBox is an internal component used by SelectField and MultiSelectField to display the list of options */
|
|
13
13
|
function ListBox(props) {
|
|
14
14
|
var _a;
|
|
15
|
-
const { state, listBoxRef, selectedOptions = [], getOptionLabel, getOptionValue, contrast = false, positionProps, } = props;
|
|
15
|
+
const { state, listBoxRef, selectedOptions = [], getOptionLabel, getOptionValue, contrast = false, positionProps, loading, } = props;
|
|
16
16
|
const { listBoxProps } = (0, react_aria_1.useListBox)({ disallowEmptySelection: true, ...props }, state, listBoxRef);
|
|
17
17
|
const positionMaxHeight = (_a = positionProps.style) === null || _a === void 0 ? void 0 : _a.maxHeight;
|
|
18
18
|
// The popoverMaxHeight will be based on the value defined by the positionProps returned from `useOverlayPosition` (which will always be a defined as a `number` based on React-Aria's `calculatePosition`).
|
|
@@ -37,7 +37,7 @@ function ListBox(props) {
|
|
|
37
37
|
// Only scroll on focus if using VirtualFocus (used for ComboBoxState (SelectField), but not SelectState (ChipSelectField))
|
|
38
38
|
scrollOnFocus: props.shouldUseVirtualFocus }, section.key)))) : ((0, jsx_runtime_1.jsx)(VirtualizedOptions_1.VirtualizedOptions, { state: state, items: [...state.collection], onListHeightChange: onListHeightChange, contrast: contrast,
|
|
39
39
|
// Only scroll on focus if using VirtualFocus (used for ComboBoxState (SelectField), but not SelectState (ChipSelectField))
|
|
40
|
-
scrollOnFocus: props.shouldUseVirtualFocus }, void 0)) }), void 0)] }), void 0));
|
|
40
|
+
scrollOnFocus: props.shouldUseVirtualFocus, loading: loading }, void 0)) }), void 0)] }), void 0));
|
|
41
41
|
}
|
|
42
42
|
exports.ListBox = ListBox;
|
|
43
43
|
// UX specified maximum height for a ListBox (in pixels)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LoadingDots = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
|
|
5
|
+
const Css_1 = require("../../Css");
|
|
6
|
+
const utils_1 = require("../../utils");
|
|
7
|
+
function LoadingDots({ contrast }) {
|
|
8
|
+
const circleCss = Css_1.Css.hPx(8)
|
|
9
|
+
.wPx(8)
|
|
10
|
+
.br4.bgColor(contrast ? Css_1.Palette.Gray500 : Css_1.Palette.Gray300)
|
|
11
|
+
.add("animationName", contrast ? "loadingDotsContrast" : "loadingDots")
|
|
12
|
+
.add("animationDuration", "800ms")
|
|
13
|
+
.add("animationIterationCount", "infinite")
|
|
14
|
+
.add("animationTimingFunction", "linear")
|
|
15
|
+
.add("animationDirection", "alternate").$;
|
|
16
|
+
const tid = (0, utils_1.useTestIds)({});
|
|
17
|
+
return ((0, jsx_runtime_1.jsx)("div", Object.assign({ css: Css_1.Css.py2.df.jcc.$ }, tid.loadingDots, { children: (0, jsx_runtime_1.jsx)("div", { "aria-label": "Loading", css: {
|
|
18
|
+
...circleCss,
|
|
19
|
+
...Css_1.Css.relative
|
|
20
|
+
.add("animationDelay", "300ms")
|
|
21
|
+
.addIn("&:before, &:after", {
|
|
22
|
+
...circleCss,
|
|
23
|
+
...Css_1.Css.add("content", "' '").absolute.dib.$,
|
|
24
|
+
})
|
|
25
|
+
.addIn("&:before", Css_1.Css.leftPx(-12).add("animationDelay", "0").$)
|
|
26
|
+
.addIn("&:after", Css_1.Css.rightPx(-12).add("animationDelay", "600ms").$).$,
|
|
27
|
+
} }, void 0) }), void 0));
|
|
28
|
+
}
|
|
29
|
+
exports.LoadingDots = LoadingDots;
|
|
@@ -2,35 +2,29 @@ import { ReactNode } from "react";
|
|
|
2
2
|
import { PresentationFieldProps } from "../../components/PresentationContext";
|
|
3
3
|
import { Value } from "../Value";
|
|
4
4
|
import { BeamFocusableProps } from "../../interfaces";
|
|
5
|
-
export interface
|
|
5
|
+
export interface BeamSelectFieldBaseProps<O, V extends Value> extends BeamFocusableProps, PresentationFieldProps {
|
|
6
6
|
/** Renders `opt` in the dropdown menu, defaults to the `getOptionLabel` prop. */
|
|
7
7
|
getOptionMenuLabel?: (opt: O) => string | ReactNode;
|
|
8
8
|
getOptionValue: (opt: O) => V;
|
|
9
9
|
getOptionLabel: (opt: O) => string;
|
|
10
10
|
/** The current value; it can be `undefined`, even if `V` cannot be. */
|
|
11
11
|
values: V[] | undefined;
|
|
12
|
-
onSelect: (values: V[]) => void;
|
|
13
|
-
options: O[];
|
|
12
|
+
onSelect: (values: V[], opts: O[]) => void;
|
|
14
13
|
multiselect?: boolean;
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Provides a non-native select/dropdown widget.
|
|
18
|
-
*
|
|
19
|
-
* The `O` type is a list of options to show, the `V` is the primitive value of a
|
|
20
|
-
* given `O` (i.e. it's id) that you want to use as the current/selected value.
|
|
21
|
-
*
|
|
22
|
-
* Note that the `V extends Key` constraint come from react-aria,
|
|
23
|
-
* and so we cannot easily change them.
|
|
24
|
-
*/
|
|
25
|
-
export declare function SelectFieldBase<O, V extends Value>(props: SelectFieldBaseProps<O, V>): JSX.Element;
|
|
26
|
-
export interface BeamSelectFieldBaseProps<T, V extends Value> extends BeamFocusableProps, PresentationFieldProps {
|
|
27
14
|
disabledOptions?: V[];
|
|
15
|
+
options: O[] | {
|
|
16
|
+
initial: O[];
|
|
17
|
+
load: () => Promise<{
|
|
18
|
+
options: O[];
|
|
19
|
+
}>;
|
|
20
|
+
};
|
|
21
|
+
/** Whether the field is disabled. If a ReactNode, it's treated as a "disabled reason" that's shown in a tooltip. */
|
|
28
22
|
disabled?: boolean | ReactNode;
|
|
29
23
|
required?: boolean;
|
|
30
24
|
errorMsg?: string;
|
|
31
25
|
helperText?: string | ReactNode;
|
|
32
26
|
/** Allow placing an icon/decoration within the input field. */
|
|
33
|
-
fieldDecoration?: (opt:
|
|
27
|
+
fieldDecoration?: (opt: O) => ReactNode;
|
|
34
28
|
/** Sets the form field label. */
|
|
35
29
|
label: string;
|
|
36
30
|
/** Renders the label inside the input field, i.e. for filters. */
|
|
@@ -46,3 +40,13 @@ export interface BeamSelectFieldBaseProps<T, V extends Value> extends BeamFocusa
|
|
|
46
40
|
/** Placeholder content */
|
|
47
41
|
placeholder?: string;
|
|
48
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Provides a non-native select/dropdown widget.
|
|
45
|
+
*
|
|
46
|
+
* The `O` type is a list of options to show, the `V` is the primitive value of a
|
|
47
|
+
* given `O` (i.e. it's id) that you want to use as the current/selected value.
|
|
48
|
+
*
|
|
49
|
+
* Note that the `V extends Key` constraint come from react-aria,
|
|
50
|
+
* and so we cannot easily change them.
|
|
51
|
+
*/
|
|
52
|
+
export declare function SelectFieldBase<O, V extends Value>(props: BeamSelectFieldBaseProps<O, V>): JSX.Element;
|
|
@@ -11,6 +11,7 @@ const Css_1 = require("../../Css");
|
|
|
11
11
|
const ListBox_1 = require("./ListBox");
|
|
12
12
|
const SelectFieldInput_1 = require("./SelectFieldInput");
|
|
13
13
|
const Value_1 = require("../Value");
|
|
14
|
+
const utils_1 = require("../../utils");
|
|
14
15
|
/**
|
|
15
16
|
* Provides a non-native select/dropdown widget.
|
|
16
17
|
*
|
|
@@ -22,16 +23,40 @@ const Value_1 = require("../Value");
|
|
|
22
23
|
*/
|
|
23
24
|
function SelectFieldBase(props) {
|
|
24
25
|
var _a;
|
|
25
|
-
const {
|
|
26
|
+
const { disabled, readOnly, onSelect, options: maybeOptions, multiselect = false, getOptionLabel, getOptionValue, getOptionMenuLabel = getOptionLabel, values = [], nothingSelectedText = "", contrast, disabledOptions, borderless, ...otherProps } = props;
|
|
26
27
|
const { contains } = (0, react_aria_1.useFilter)({ sensitivity: "base" });
|
|
27
28
|
const isDisabled = !!disabled;
|
|
28
29
|
const isReadOnly = !!readOnly;
|
|
29
|
-
|
|
30
|
+
const [fieldState, setFieldState] = (0, react_1.useState)(() => {
|
|
30
31
|
var _a;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
const initOptions = Array.isArray(maybeOptions) ? maybeOptions : maybeOptions.initial;
|
|
33
|
+
const selectedOptions = initOptions.filter((o) => values.includes(getOptionValue(o)));
|
|
34
|
+
return {
|
|
35
|
+
isOpen: false,
|
|
36
|
+
selectedKeys: (_a = selectedOptions === null || selectedOptions === void 0 ? void 0 : selectedOptions.map((o) => (0, Value_1.valueToKey)(getOptionValue(o)))) !== null && _a !== void 0 ? _a : [],
|
|
37
|
+
inputValue: getInputValue(initOptions.filter((o) => values === null || values === void 0 ? void 0 : values.includes(getOptionValue(o))), getOptionLabel, multiselect, nothingSelectedText),
|
|
38
|
+
filteredOptions: initOptions,
|
|
39
|
+
allOptions: initOptions,
|
|
40
|
+
selectedOptions: selectedOptions,
|
|
41
|
+
optionsLoading: false,
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
/** Resets field's input value and filtered options list for cases where the user exits the field without making changes (on Escape, or onBlur) */
|
|
45
|
+
function resetField() {
|
|
46
|
+
const inputValue = getInputValue(fieldState.allOptions.filter((o) => values === null || values === void 0 ? void 0 : values.includes(getOptionValue(o))), getOptionLabel, multiselect, nothingSelectedText);
|
|
47
|
+
// Conditionally reset the value if the current inputValue doesn't match that of the passed in value, or we filtered the list
|
|
48
|
+
if (inputValue !== fieldState.inputValue || fieldState.filteredOptions.length !== fieldState.allOptions.length) {
|
|
49
|
+
setFieldState((prevState) => ({
|
|
50
|
+
...prevState,
|
|
51
|
+
isOpen: false,
|
|
52
|
+
inputValue,
|
|
53
|
+
filteredOptions: prevState.allOptions,
|
|
54
|
+
}));
|
|
34
55
|
}
|
|
56
|
+
}
|
|
57
|
+
function onSelectionChange(keys) {
|
|
58
|
+
var _a;
|
|
59
|
+
// We don't currently handle the "all" case
|
|
35
60
|
if (keys === "all") {
|
|
36
61
|
return;
|
|
37
62
|
}
|
|
@@ -41,7 +66,6 @@ function SelectFieldBase(props) {
|
|
|
41
66
|
const selectionChanged = !(keys.size === state.selectionManager.selectedKeys.size &&
|
|
42
67
|
[...keys].every((value) => state.selectionManager.selectedKeys.has(value)));
|
|
43
68
|
if (multiselect && keys.size === 0) {
|
|
44
|
-
// "All" happens if we selected everything or nothing.
|
|
45
69
|
setFieldState({
|
|
46
70
|
...fieldState,
|
|
47
71
|
isOpen: true,
|
|
@@ -49,70 +73,73 @@ function SelectFieldBase(props) {
|
|
|
49
73
|
selectedKeys: [],
|
|
50
74
|
selectedOptions: [],
|
|
51
75
|
});
|
|
52
|
-
selectionChanged && onSelect([]);
|
|
76
|
+
selectionChanged && onSelect([], []);
|
|
53
77
|
return;
|
|
54
78
|
}
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
inputValue: firstSelectedOption ? getOptionLabel(firstSelectedOption) : "",
|
|
74
|
-
selectedKeys: [firstKey],
|
|
75
|
-
selectedOptions: firstSelectedOption ? [firstSelectedOption] : [],
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
selectionChanged && onSelect([...keys.values()].map(Value_1.keyToValue));
|
|
79
|
+
const selectedKeys = [...keys.values()];
|
|
80
|
+
const selectedOptions = fieldState.allOptions.filter((o) => selectedKeys.includes((0, Value_1.valueToKey)(getOptionValue(o))));
|
|
81
|
+
const firstSelectedOption = selectedOptions[0];
|
|
82
|
+
setFieldState((prevState) => ({
|
|
83
|
+
...prevState,
|
|
84
|
+
// Close menu upon selection change only for Single selection mode
|
|
85
|
+
isOpen: multiselect,
|
|
86
|
+
// If menu is open then reset inputValue to "". Otherwise set inputValue depending on number of options selected.
|
|
87
|
+
inputValue: multiselect && (state.isOpen || selectedKeys.length > 1)
|
|
88
|
+
? ""
|
|
89
|
+
: firstSelectedOption
|
|
90
|
+
? getOptionLabel(firstSelectedOption)
|
|
91
|
+
: "",
|
|
92
|
+
selectedKeys,
|
|
93
|
+
selectedOptions,
|
|
94
|
+
filteredOptions: fieldState.allOptions,
|
|
95
|
+
}));
|
|
96
|
+
selectionChanged && onSelect(selectedKeys.map(Value_1.keyToValue), selectedOptions);
|
|
79
97
|
if (!multiselect) {
|
|
80
98
|
// When a single select menu item changes, then blur the field AFTER `onSelect` has been called
|
|
81
99
|
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.blur();
|
|
82
100
|
}
|
|
83
101
|
}
|
|
84
102
|
function onInputChange(value) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
103
|
+
if (value !== fieldState.inputValue) {
|
|
104
|
+
setFieldState((prevState) => ({
|
|
105
|
+
...prevState,
|
|
106
|
+
inputValue: value,
|
|
107
|
+
filteredOptions: fieldState.allOptions.filter((o) => contains(getOptionLabel(o), value)),
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async function maybeInitLoad() {
|
|
112
|
+
if (!Array.isArray(maybeOptions)) {
|
|
113
|
+
setFieldState((prevState) => ({ ...prevState, optionsLoading: true }));
|
|
114
|
+
const { options } = await maybeOptions.load();
|
|
115
|
+
setFieldState((prevState) => ({
|
|
116
|
+
...prevState,
|
|
117
|
+
filteredOptions: options,
|
|
118
|
+
allOptions: options,
|
|
119
|
+
optionsLoading: false,
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
91
122
|
}
|
|
123
|
+
const firstOpen = (0, react_1.useRef)(true);
|
|
92
124
|
function onOpenChange(isOpen) {
|
|
125
|
+
if (firstOpen.current && isOpen) {
|
|
126
|
+
maybeInitLoad();
|
|
127
|
+
firstOpen.current = false;
|
|
128
|
+
}
|
|
93
129
|
setFieldState((prevState) => ({
|
|
94
130
|
...prevState,
|
|
131
|
+
// When using the multiselect field, always empty the input upon open.
|
|
95
132
|
inputValue: multiselect && isOpen ? "" : prevState.inputValue,
|
|
96
133
|
isOpen,
|
|
97
134
|
}));
|
|
98
135
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
inputValue: selectedOptions.length === 1
|
|
107
|
-
? getOptionLabel(selectedOptions[0])
|
|
108
|
-
: multiselect && selectedOptions.length === 0
|
|
109
|
-
? nothingSelectedText
|
|
110
|
-
: "",
|
|
111
|
-
filteredOptions: options,
|
|
112
|
-
selectedOptions: selectedOptions,
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
const [fieldState, setFieldState] = (0, react_1.useState)(initFieldState);
|
|
136
|
+
// Used to calculate the rendered width of the combo box (input + button)
|
|
137
|
+
const comboBoxRef = (0, react_1.useRef)(null);
|
|
138
|
+
const triggerRef = (0, react_1.useRef)(null);
|
|
139
|
+
const inputRef = (0, react_1.useRef)(null);
|
|
140
|
+
const inputWrapRef = (0, react_1.useRef)(null);
|
|
141
|
+
const listBoxRef = (0, react_1.useRef)(null);
|
|
142
|
+
const popoverRef = (0, react_1.useRef)(null);
|
|
116
143
|
const comboBoxProps = {
|
|
117
144
|
...otherProps,
|
|
118
145
|
disabledKeys: disabledOptions === null || disabledOptions === void 0 ? void 0 : disabledOptions.map(Value_1.valueToKey),
|
|
@@ -120,7 +147,6 @@ function SelectFieldBase(props) {
|
|
|
120
147
|
items: fieldState.filteredOptions,
|
|
121
148
|
isDisabled,
|
|
122
149
|
isReadOnly,
|
|
123
|
-
label,
|
|
124
150
|
onInputChange,
|
|
125
151
|
onOpenChange,
|
|
126
152
|
menuTrigger: "focus",
|
|
@@ -128,40 +154,68 @@ function SelectFieldBase(props) {
|
|
|
128
154
|
};
|
|
129
155
|
const state = (0, react_stately_1.useComboBoxState)({
|
|
130
156
|
...comboBoxProps,
|
|
157
|
+
allowsEmptyCollection: true,
|
|
131
158
|
// useComboBoxState.onSelectionChange will be executed if a keyboard interaction (Enter key) is used to select an item
|
|
132
159
|
onSelectionChange: (key) => {
|
|
133
160
|
// ignore undefined/null keys - `null` can happen if input field's value is completely deleted after having a value assigned.
|
|
134
161
|
if (key) {
|
|
135
|
-
const selectedKeys =
|
|
162
|
+
const selectedKeys = state.selectionManager.selectedKeys;
|
|
136
163
|
// Create the `newSelection` Set depending on the value type of SelectField.
|
|
137
164
|
const newSelection = new Set(!multiselect ? [key] : [...selectedKeys, key]);
|
|
138
165
|
// Use only the `multipleSelectionState` to manage selected keys
|
|
139
|
-
|
|
166
|
+
state.selectionManager.setSelectedKeys(newSelection);
|
|
140
167
|
}
|
|
141
168
|
},
|
|
142
169
|
});
|
|
143
|
-
|
|
170
|
+
//@ts-ignore - `selectionManager.state` exists, but not according to the types
|
|
171
|
+
state.selectionManager.state = (0, react_stately_1.useMultipleSelectionState)({
|
|
144
172
|
selectionMode: multiselect ? "multiple" : "single",
|
|
145
173
|
// Do not allow an empty selection if single select mode
|
|
146
174
|
disallowEmptySelection: !multiselect,
|
|
147
175
|
selectedKeys: fieldState.selectedKeys,
|
|
148
176
|
onSelectionChange,
|
|
149
177
|
});
|
|
150
|
-
//@ts-ignore - `selectionManager.state` exists, but not according to the types
|
|
151
|
-
state.selectionManager.state = multipleSelectionState;
|
|
152
178
|
// Ensure we reset if the field's values change and the user is not actively selecting options.
|
|
153
179
|
(0, react_1.useEffect)(() => {
|
|
154
|
-
if (!state.isOpen) {
|
|
155
|
-
setFieldState(
|
|
180
|
+
if (!state.isOpen && !(0, utils_1.areArraysEqual)(values, fieldState.selectedKeys)) {
|
|
181
|
+
setFieldState((prevState) => {
|
|
182
|
+
var _a;
|
|
183
|
+
const selectedOptions = prevState.allOptions.filter((o) => values === null || values === void 0 ? void 0 : values.includes(getOptionValue(o)));
|
|
184
|
+
return {
|
|
185
|
+
...prevState,
|
|
186
|
+
selectedKeys: (_a = selectedOptions === null || selectedOptions === void 0 ? void 0 : selectedOptions.map((o) => (0, Value_1.valueToKey)(getOptionValue(o)))) !== null && _a !== void 0 ? _a : [],
|
|
187
|
+
inputValue: selectedOptions.length === 1
|
|
188
|
+
? getOptionLabel(selectedOptions[0])
|
|
189
|
+
: multiselect && selectedOptions.length === 0
|
|
190
|
+
? nothingSelectedText
|
|
191
|
+
: "",
|
|
192
|
+
selectedOptions: selectedOptions,
|
|
193
|
+
};
|
|
194
|
+
});
|
|
156
195
|
}
|
|
157
196
|
}, [values]);
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
197
|
+
(0, react_1.useEffect)(() => {
|
|
198
|
+
// Only update the fieldset when options change, when options is an array.
|
|
199
|
+
// Otherwise, if the options are passed in as an object, then we assume the caller is updating options via a Promise and not via updating props.
|
|
200
|
+
if (Array.isArray(maybeOptions) && maybeOptions !== fieldState.allOptions) {
|
|
201
|
+
setFieldState((prevState) => {
|
|
202
|
+
var _a;
|
|
203
|
+
const selectedOptions = maybeOptions.filter((o) => values === null || values === void 0 ? void 0 : values.includes(getOptionValue(o)));
|
|
204
|
+
return {
|
|
205
|
+
...prevState,
|
|
206
|
+
selectedKeys: (_a = selectedOptions === null || selectedOptions === void 0 ? void 0 : selectedOptions.map((o) => (0, Value_1.valueToKey)(getOptionValue(o)))) !== null && _a !== void 0 ? _a : [],
|
|
207
|
+
inputValue: selectedOptions.length === 1
|
|
208
|
+
? getOptionLabel(selectedOptions[0])
|
|
209
|
+
: multiselect && selectedOptions.length === 0
|
|
210
|
+
? nothingSelectedText
|
|
211
|
+
: "",
|
|
212
|
+
selectedOptions: selectedOptions,
|
|
213
|
+
filteredOptions: maybeOptions,
|
|
214
|
+
allOptions: maybeOptions,
|
|
215
|
+
};
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}, [maybeOptions]);
|
|
165
219
|
// For the most part, the returned props contain `aria-*` and `id` attributes for accessibility purposes.
|
|
166
220
|
const { buttonProps: triggerProps, inputProps, listBoxProps, labelProps, } = (0, react_aria_1.useComboBox)({
|
|
167
221
|
...comboBoxProps,
|
|
@@ -188,6 +242,13 @@ function SelectFieldBase(props) {
|
|
|
188
242
|
// Ensures the menu never gets too small.
|
|
189
243
|
minWidth: 200,
|
|
190
244
|
};
|
|
191
|
-
return ((0, jsx_runtime_1.jsxs)("div", Object.assign({ css: Css_1.Css.df.fdc.w100.maxw((0, Css_1.px)(550)).$, ref: comboBoxRef }, { children: [(0, jsx_runtime_1.jsx)(SelectFieldInput_1.SelectFieldInput, Object.assign({}, otherProps, { buttonProps: buttonProps, buttonRef: triggerRef,
|
|
245
|
+
return ((0, jsx_runtime_1.jsxs)("div", Object.assign({ css: Css_1.Css.df.fdc.w100.maxw((0, Css_1.px)(550)).$, ref: comboBoxRef }, { children: [(0, jsx_runtime_1.jsx)(SelectFieldInput_1.SelectFieldInput, Object.assign({}, otherProps, { buttonProps: buttonProps, buttonRef: triggerRef, inputProps: inputProps, inputRef: inputRef, inputWrapRef: inputWrapRef, state: state, labelProps: labelProps, selectedOptions: fieldState.selectedOptions, getOptionValue: getOptionValue, getOptionLabel: getOptionLabel, contrast: contrast, nothingSelectedText: nothingSelectedText, borderless: borderless, tooltip: (0, components_1.resolveTooltip)(disabled, undefined, readOnly), resetField: resetField }), void 0), state.isOpen && ((0, jsx_runtime_1.jsx)(internal_1.Popover, Object.assign({ triggerRef: triggerRef, popoverRef: popoverRef, positionProps: positionProps, onClose: () => state.close(), isOpen: state.isOpen, minWidth: 200 }, { children: (0, jsx_runtime_1.jsx)(ListBox_1.ListBox, Object.assign({}, listBoxProps, { positionProps: positionProps, state: state, listBoxRef: listBoxRef, selectedOptions: fieldState.selectedOptions, getOptionLabel: getOptionLabel, getOptionValue: (o) => (0, Value_1.valueToKey)(getOptionValue(o)), contrast: contrast, loading: fieldState.optionsLoading }), void 0) }), void 0))] }), void 0));
|
|
192
246
|
}
|
|
193
247
|
exports.SelectFieldBase = SelectFieldBase;
|
|
248
|
+
function getInputValue(selectedOptions, getOptionLabel, multiselect, nothingSelectedText) {
|
|
249
|
+
return selectedOptions.length === 1
|
|
250
|
+
? getOptionLabel(selectedOptions[0])
|
|
251
|
+
: multiselect && selectedOptions.length === 0
|
|
252
|
+
? nothingSelectedText
|
|
253
|
+
: "";
|
|
254
|
+
}
|
|
@@ -2,6 +2,7 @@ import { InputHTMLAttributes, LabelHTMLAttributes, MutableRefObject, ReactNode }
|
|
|
2
2
|
import { ComboBoxState } from "react-stately";
|
|
3
3
|
import { PresentationFieldProps } from "../../components/PresentationContext";
|
|
4
4
|
import { Value } from "../Value";
|
|
5
|
+
import { Callback } from "../../types";
|
|
5
6
|
interface SelectFieldInputProps<O, V extends Value> extends PresentationFieldProps {
|
|
6
7
|
buttonProps: any;
|
|
7
8
|
buttonRef: MutableRefObject<HTMLButtonElement | null>;
|
|
@@ -9,8 +10,6 @@ interface SelectFieldInputProps<O, V extends Value> extends PresentationFieldPro
|
|
|
9
10
|
inputRef: MutableRefObject<HTMLInputElement | null>;
|
|
10
11
|
inputWrapRef: MutableRefObject<HTMLDivElement | null>;
|
|
11
12
|
state: ComboBoxState<O>;
|
|
12
|
-
isDisabled: boolean;
|
|
13
|
-
isReadOnly: boolean;
|
|
14
13
|
fieldDecoration?: (opt: O) => ReactNode;
|
|
15
14
|
errorMsg?: string;
|
|
16
15
|
required?: boolean;
|
|
@@ -23,10 +22,11 @@ interface SelectFieldInputProps<O, V extends Value> extends PresentationFieldPro
|
|
|
23
22
|
selectedOptions: O[];
|
|
24
23
|
getOptionValue: (opt: O) => V;
|
|
25
24
|
getOptionLabel: (opt: O) => string;
|
|
26
|
-
sizeToContent
|
|
25
|
+
sizeToContent?: boolean;
|
|
27
26
|
contrast?: boolean;
|
|
28
27
|
nothingSelectedText: string;
|
|
29
28
|
tooltip?: ReactNode;
|
|
29
|
+
resetField: Callback;
|
|
30
30
|
}
|
|
31
31
|
export declare function SelectFieldInput<O, V extends Value>(props: SelectFieldInputProps<O, V>): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
32
32
|
export {};
|
|
@@ -7,19 +7,18 @@ const react_aria_1 = require("react-aria");
|
|
|
7
7
|
const components_1 = require("../../components");
|
|
8
8
|
const Css_1 = require("../../Css");
|
|
9
9
|
const TextFieldBase_1 = require("../TextFieldBase");
|
|
10
|
-
const Value_1 = require("../Value");
|
|
11
10
|
const utils_1 = require("../../utils");
|
|
12
11
|
function SelectFieldInput(props) {
|
|
13
|
-
const { inputProps,
|
|
12
|
+
const { inputProps, buttonProps, buttonRef, errorMsg, state, fieldDecoration, onBlur, onFocus, inlineLabel, selectedOptions, getOptionValue, getOptionLabel, sizeToContent = false, contrast = false, nothingSelectedText, resetField, ...otherProps } = props;
|
|
14
13
|
const [isFocused, setIsFocused] = (0, react_1.useState)(false);
|
|
15
14
|
const isMultiSelect = state.selectionManager.selectionMode === "multiple";
|
|
16
15
|
const showNumSelection = isMultiSelect && state.selectionManager.selectedKeys.size > 1;
|
|
17
16
|
// For MultiSelect only show the `fieldDecoration` when input is not in focus.
|
|
18
17
|
const showFieldDecoration = (!isMultiSelect || (isMultiSelect && !isFocused)) && fieldDecoration && selectedOptions.length === 1;
|
|
19
|
-
return ((0, jsx_runtime_1.jsx)(TextFieldBase_1.TextFieldBase, Object.assign({}, otherProps, {
|
|
20
|
-
(showFieldDecoration && fieldDecoration(selectedOptions[0])), endAdornment: !
|
|
18
|
+
return ((0, jsx_runtime_1.jsx)(TextFieldBase_1.TextFieldBase, Object.assign({}, otherProps, { readOnly: inputProps.readOnly, inlineLabel: inlineLabel, errorMsg: errorMsg, contrast: contrast, xss: !inlineLabel && !inputProps.readOnly ? Css_1.Css.fw5.$ : {}, startAdornment: (showNumSelection && ((0, jsx_runtime_1.jsx)("span", Object.assign({ css: Css_1.Css.wPx(16).hPx(16).fs0.br100.bgLightBlue700.white.tinyEm.df.aic.jcc.$ }, { children: state.selectionManager.selectedKeys.size }), void 0))) ||
|
|
19
|
+
(showFieldDecoration && fieldDecoration(selectedOptions[0])), endAdornment: !inputProps.readOnly && ((0, jsx_runtime_1.jsx)("button", Object.assign({}, buttonProps, { disabled: inputProps.disabled, ref: buttonRef, css: {
|
|
21
20
|
...Css_1.Css.br4.outline0.gray700.if(contrast).gray400.$,
|
|
22
|
-
...(
|
|
21
|
+
...(inputProps.disabled ? Css_1.Css.cursorNotAllowed.gray400.if(contrast).gray600.$ : {}),
|
|
23
22
|
} }, { children: (0, jsx_runtime_1.jsx)(components_1.Icon, { icon: state.isOpen ? "chevronUp" : "chevronDown" }, void 0) }), void 0)), inputProps: {
|
|
24
23
|
...(0, react_aria_1.mergeProps)(inputProps, { "aria-invalid": Boolean(errorMsg), onInput: () => state.open() }),
|
|
25
24
|
// Not merging the following as we want them to overwrite existing events
|
|
@@ -37,7 +36,7 @@ function SelectFieldInput(props) {
|
|
|
37
36
|
return;
|
|
38
37
|
}
|
|
39
38
|
// By default, the Escape key would "revert" changes,
|
|
40
|
-
// but we just want to close the menu and leave the
|
|
39
|
+
// but we just want to close the menu and leave the reset of the field state as is.
|
|
41
40
|
if (e.key === "Escape") {
|
|
42
41
|
state.close();
|
|
43
42
|
return;
|
|
@@ -48,31 +47,31 @@ function SelectFieldInput(props) {
|
|
|
48
47
|
// reset the field to its previous value. However, because we use a the Multiple Selection State manager,
|
|
49
48
|
// then our `state.selectedKey` isn't set. So we need to properly reset the state ourselves.
|
|
50
49
|
if (e.key === "Escape") {
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
state.close();
|
|
51
|
+
resetField();
|
|
53
52
|
return;
|
|
54
53
|
}
|
|
55
54
|
inputProps.onKeyDown && inputProps.onKeyDown(e);
|
|
56
55
|
},
|
|
57
|
-
onBlur: () => {
|
|
56
|
+
onBlur: (e) => {
|
|
57
|
+
// Do not call onBlur if readOnly or interacting within the input wrapper (such as the menu trigger button).
|
|
58
|
+
if (inputProps.readOnly ||
|
|
59
|
+
(props.inputWrapRef.current && props.inputWrapRef.current.contains(e.relatedTarget))) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
58
62
|
// We purposefully override onBlur here instead of using mergeProps, b/c inputProps.onBlur
|
|
59
63
|
// goes into useComboBox's onBlur, which calls setFocused(false), which in useComboBoxState
|
|
60
64
|
// detects a) there is no props.selectedKey (b/c we don't pass it), and b) there is an
|
|
61
65
|
// `inputValue`, so it thinks it needs to call `resetInputValue()`.
|
|
62
|
-
//
|
|
63
|
-
// I assume we don't pass `selectedKey` b/c we support multiple keys.
|
|
64
|
-
if (isReadOnly) {
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
66
|
setIsFocused(false);
|
|
68
67
|
(0, utils_1.maybeCall)(onBlur);
|
|
69
68
|
state.close();
|
|
70
|
-
// Always call `
|
|
71
|
-
//
|
|
72
|
-
|
|
69
|
+
// Always call `resetField` onBlur, this ensures the field's `input.value` resets
|
|
70
|
+
// to what it should be in case it doesn't currently match.
|
|
71
|
+
resetField();
|
|
73
72
|
},
|
|
74
73
|
onFocus: () => {
|
|
75
|
-
if (
|
|
74
|
+
if (inputProps.readOnly)
|
|
76
75
|
return;
|
|
77
76
|
setIsFocused(true);
|
|
78
77
|
(0, utils_1.maybeCall)(onFocus);
|
|
@@ -6,6 +6,7 @@ interface VirtualizedOptionsProps<O> {
|
|
|
6
6
|
onListHeightChange: (height: number) => void;
|
|
7
7
|
contrast: boolean;
|
|
8
8
|
scrollOnFocus?: boolean;
|
|
9
|
+
loading?: boolean | (() => JSX.Element);
|
|
9
10
|
}
|
|
10
11
|
export declare function VirtualizedOptions<O>(props: VirtualizedOptionsProps<O>): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
11
12
|
export {};
|
|
@@ -4,10 +4,11 @@ exports.VirtualizedOptions = void 0;
|
|
|
4
4
|
const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
|
|
5
5
|
const react_1 = require("react");
|
|
6
6
|
const react_virtuoso_1 = require("react-virtuoso");
|
|
7
|
+
const LoadingDots_1 = require("./LoadingDots");
|
|
7
8
|
const Option_1 = require("./Option");
|
|
8
9
|
// Displays ListBox options in a virtualized container for performance reasons
|
|
9
10
|
function VirtualizedOptions(props) {
|
|
10
|
-
const { state, items, onListHeightChange, contrast, scrollOnFocus } = props;
|
|
11
|
+
const { state, items, onListHeightChange, contrast, scrollOnFocus, loading } = props;
|
|
11
12
|
const virtuosoRef = (0, react_1.useRef)(null);
|
|
12
13
|
const focusedItem = state.collection.getItem(state.selectionManager.focusedKey);
|
|
13
14
|
const selectedItem = state.selectionManager.selectedKeys.size > 0
|
|
@@ -33,6 +34,10 @@ function VirtualizedOptions(props) {
|
|
|
33
34
|
// Only send scrollToIndex functionality forward if we are not auto-scrolling on focus.
|
|
34
35
|
scrollToIndex: scrollOnFocus ? undefined : (_a = virtuosoRef.current) === null || _a === void 0 ? void 0 : _a.scrollToIndex }, item.key));
|
|
35
36
|
}
|
|
36
|
-
}
|
|
37
|
+
}, components: !loading
|
|
38
|
+
? {}
|
|
39
|
+
: {
|
|
40
|
+
Footer: typeof loading === "function" ? loading : () => (0, jsx_runtime_1.jsx)(LoadingDots_1.LoadingDots, { contrast: contrast }, void 0),
|
|
41
|
+
} }, void 0));
|
|
37
42
|
}
|
|
38
43
|
exports.VirtualizedOptions = VirtualizedOptions;
|
package/dist/utils/index.d.ts
CHANGED
package/dist/utils/index.js
CHANGED
|
@@ -10,7 +10,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
10
10
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
11
11
|
};
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
-
exports.isAbsoluteUrl = exports.EmptyRef = exports.safeEntries = exports.noop = exports.omitKey = exports.safeKeys = exports.maybeCall = exports.toGroupState = exports.toToggleState = void 0;
|
|
13
|
+
exports.areArraysEqual = exports.isAbsoluteUrl = exports.EmptyRef = exports.safeEntries = exports.noop = exports.omitKey = exports.safeKeys = exports.maybeCall = exports.toGroupState = exports.toToggleState = void 0;
|
|
14
14
|
/** Adapts our state to what useToggleState returns in a stateless manner. */
|
|
15
15
|
function toToggleState(isSelected, onChange) {
|
|
16
16
|
return {
|
|
@@ -74,3 +74,7 @@ class EmptyRef {
|
|
|
74
74
|
exports.EmptyRef = EmptyRef;
|
|
75
75
|
const isAbsoluteUrl = (url) => /^(http(s?)):\/\//i.test(url);
|
|
76
76
|
exports.isAbsoluteUrl = isAbsoluteUrl;
|
|
77
|
+
function areArraysEqual(a, b) {
|
|
78
|
+
return a.length === b.length && a.every((val, idx) => val === b[idx]);
|
|
79
|
+
}
|
|
80
|
+
exports.areArraysEqual = areArraysEqual;
|
package/dist/utils/sb.js
CHANGED
|
@@ -6,7 +6,7 @@ const components_1 = require("../components");
|
|
|
6
6
|
const Css_1 = require("../Css");
|
|
7
7
|
const rtl_1 = require("./rtl");
|
|
8
8
|
function withRouter(url, path) {
|
|
9
|
-
return (
|
|
9
|
+
return (Story) => (0, rtl_1.withRouter)(url, path).wrap((0, jsx_runtime_1.jsx)(Story, {}, void 0));
|
|
10
10
|
}
|
|
11
11
|
exports.withRouter = withRouter;
|
|
12
12
|
/** A somewhat typesafe way to set `FooStory.story` metadata. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@homebound/beam",
|
|
3
|
-
"version": "2.97.
|
|
3
|
+
"version": "2.97.4",
|
|
4
4
|
"author": "Homebound",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"format": "prettier --loglevel warn --write \"**/*.{ts,tsx,css,md}\""
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@homebound/form-state": "^2.
|
|
36
|
+
"@homebound/form-state": "^2.2.13",
|
|
37
37
|
"@react-aria/utils": "^3.9.0",
|
|
38
38
|
"@react-hook/resize-observer": "^1.2.2",
|
|
39
39
|
"@types/tinycolor2": "^1.4.2",
|
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@babel/core": "^7.15.5",
|
|
73
73
|
"@babel/plugin-proposal-class-properties": "^7.14.5",
|
|
74
|
+
"@babel/preset-env": "^7.16.5",
|
|
74
75
|
"@babel/preset-typescript": "^7.15.0",
|
|
75
76
|
"@emotion/babel-preset-css-prop": "^11.2.0",
|
|
76
77
|
"@emotion/jest": "^11.3.0",
|
|
@@ -79,10 +80,12 @@
|
|
|
79
80
|
"@homebound/rtl-utils": "^2.51.0",
|
|
80
81
|
"@homebound/tsconfig": "^1.0.3",
|
|
81
82
|
"@semantic-release/git": "^9.0.0",
|
|
82
|
-
"@storybook/addon-essentials": "^6.
|
|
83
|
-
"@storybook/addon-
|
|
84
|
-
"@storybook/
|
|
85
|
-
"@storybook/
|
|
83
|
+
"@storybook/addon-essentials": "^6.4.9",
|
|
84
|
+
"@storybook/addon-interactions": "^6.4.9",
|
|
85
|
+
"@storybook/addon-links": "^6.4.9",
|
|
86
|
+
"@storybook/addons": "^6.4.9",
|
|
87
|
+
"@storybook/react": "^6.4.9",
|
|
88
|
+
"@storybook/testing-library": "^0.0.7",
|
|
86
89
|
"@testing-library/jest-dom": "^5.11.9",
|
|
87
90
|
"@testing-library/react-hooks": "^7.0.1",
|
|
88
91
|
"@tsconfig/recommended": "^1.0.1",
|