@homebound/beam 2.319.1 → 2.320.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Filters/SingleFilter.js +1 -1
- package/dist/inputs/SelectField.js +3 -1
- package/dist/inputs/TreeSelectField/TreeSelectField.js +1 -1
- package/dist/inputs/TreeSelectField/utils.d.ts +1 -1
- package/dist/inputs/internal/ComboBoxBase.d.ts +11 -5
- package/dist/inputs/internal/ComboBoxBase.js +64 -69
- package/package.json +1 -1
|
@@ -15,7 +15,7 @@ class SingleFilter extends BaseFilter_1.BaseFilter {
|
|
|
15
15
|
const { label, defaultValue, options: maybeOptions, getOptionLabel, getOptionValue, nothingSelectedText, ...props } = this.props;
|
|
16
16
|
const options = Array.isArray(maybeOptions)
|
|
17
17
|
? [allOption, ...maybeOptions]
|
|
18
|
-
: { ...maybeOptions,
|
|
18
|
+
: { ...maybeOptions, current: maybeOptions.current };
|
|
19
19
|
return ((0, jsx_runtime_1.jsx)(SelectField_1.SelectField, { ...props, options: options, getOptionValue: (o) => (o === allOption ? undefined : getOptionValue(o)), getOptionLabel: (o) => (o === allOption ? nothingSelectedText !== null && nothingSelectedText !== void 0 ? nothingSelectedText : "All" : getOptionLabel(o)), compact: !vertical, value: value, label: this.label, labelStyle: inModal ? "hidden" : !inModal && !vertical ? "inline" : "above", sizeToContent: !inModal && !vertical, nothingSelectedText: nothingSelectedText !== null && nothingSelectedText !== void 0 ? nothingSelectedText : "All", onSelect: (value) => setValue(value || undefined), ...this.testId(tid) }));
|
|
20
20
|
}
|
|
21
21
|
}
|
|
@@ -2,12 +2,14 @@
|
|
|
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 ComboBoxBase_1 = require("./internal/ComboBoxBase");
|
|
6
7
|
function SelectField(props) {
|
|
7
8
|
const { getOptionValue = (opt) => opt.id, // if unset, assume O implements HasId
|
|
8
9
|
getOptionLabel = (opt) => opt.name, // if unset, assume O implements HasName
|
|
9
10
|
options, onSelect, value, ...otherProps } = props;
|
|
10
|
-
|
|
11
|
+
const values = (0, react_1.useMemo)(() => [value], [value]);
|
|
12
|
+
return ((0, jsx_runtime_1.jsx)(ComboBoxBase_1.ComboBoxBase, { ...otherProps, options: options, getOptionLabel: getOptionLabel, getOptionValue: getOptionValue, values: values, onSelect: (values, options) => {
|
|
11
13
|
// If the user used `unsetLabel`, then values will be `[undefined]` and options `[unsetOption]`
|
|
12
14
|
if (values.length > 0 && options.length > 0) {
|
|
13
15
|
const option = options[0];
|
|
@@ -64,7 +64,7 @@ function TreeSelectFieldBase(props) {
|
|
|
64
64
|
const { values, options, getOptionValue, getOptionLabel, getOptionMenuLabel = getOptionLabel, disabled, readOnly, labelStyle, borderless, contrast = false, nothingSelectedText = "", onSelect, defaultCollapsed = false, placeholder, ...otherProps } = props;
|
|
65
65
|
const isDisabled = !!disabled;
|
|
66
66
|
const isReadOnly = !!readOnly;
|
|
67
|
-
const initialOptions = Array.isArray(options) ? options : options.
|
|
67
|
+
const initialOptions = Array.isArray(options) ? options : options.current;
|
|
68
68
|
const { contains } = (0, react_aria_1.useFilter)({ sensitivity: "base" });
|
|
69
69
|
const { collapsedKeys } = useTreeSelectFieldProvider();
|
|
70
70
|
function levelOptions(o, level, filtering) {
|
|
@@ -2,6 +2,7 @@ import React, { ReactNode } from "react";
|
|
|
2
2
|
import { PresentationFieldProps } from "../../components/PresentationContext";
|
|
3
3
|
import { Value } from "../Value";
|
|
4
4
|
import { BeamFocusableProps } from "../../interfaces";
|
|
5
|
+
/** Base props for either `SelectField` or `MultiSelectField`. */
|
|
5
6
|
export interface ComboBoxBaseProps<O, V extends Value> extends BeamFocusableProps, PresentationFieldProps {
|
|
6
7
|
/** Renders `opt` in the dropdown menu, defaults to the `getOptionLabel` prop. `isUnsetOpt` is only defined for single SelectField */
|
|
7
8
|
getOptionMenuLabel?: (opt: O, isUnsetOpt?: boolean) => string | ReactNode;
|
|
@@ -62,13 +63,18 @@ export interface ComboBoxBaseProps<O, V extends Value> extends BeamFocusableProp
|
|
|
62
63
|
* and so we cannot easily change them.
|
|
63
64
|
*/
|
|
64
65
|
export declare function ComboBoxBase<O, V extends Value>(props: ComboBoxBaseProps<O, V>): JSX.Element;
|
|
66
|
+
/** Allows lazy-loading select fields, which is useful for pages w/lots of fields the user may not actually use. */
|
|
65
67
|
export type OptionsOrLoad<O> = O[] | {
|
|
66
|
-
initial
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
/** The initial option to show before the user interacts with the dropdown. */
|
|
69
|
+
current: O | undefined;
|
|
70
|
+
/** Fired when the user interacts with the dropdown, to load the real options. */
|
|
71
|
+
load: () => Promise<unknown>;
|
|
72
|
+
/** The full list of options, after load() has been fired. */
|
|
73
|
+
options: O[] | undefined;
|
|
70
74
|
};
|
|
71
|
-
|
|
75
|
+
/** Transforms/simplifies `optionsOrLoad` into just options, with unsetLabel maybe added. */
|
|
76
|
+
export declare function initializeOptions<O, V extends Value>(optionsOrLoad: OptionsOrLoad<O>, getOptionValue: (opt: O) => V, unsetLabel: string | undefined): O[];
|
|
77
|
+
/** A marker option to automatically add an "Unset" option to the start of options. */
|
|
72
78
|
export declare const unsetOption: {};
|
|
73
79
|
export declare function disabledOptionToKeyedTuple(disabledOption: Value | {
|
|
74
80
|
value: Value;
|
|
@@ -25,38 +25,31 @@ const utils_1 = require("../../utils");
|
|
|
25
25
|
function ComboBoxBase(props) {
|
|
26
26
|
var _a, _b, _c, _d;
|
|
27
27
|
const { fieldProps } = (0, PresentationContext_1.usePresentationContext)();
|
|
28
|
-
const { disabled, readOnly, onSelect, options, multiselect = false, values = [], nothingSelectedText = "", contrast, disabledOptions, borderless, unsetLabel, ...otherProps } = props;
|
|
28
|
+
const { disabled, readOnly, onSelect, options: propOptions, multiselect = false, values = [], nothingSelectedText = "", contrast, disabledOptions, borderless, unsetLabel, getOptionLabel: propOptionLabel, getOptionValue: propOptionValue, getOptionMenuLabel: propOptionMenuLabel, ...otherProps } = props;
|
|
29
29
|
const labelStyle = (_b = (_a = otherProps.labelStyle) !== null && _a !== void 0 ? _a : fieldProps === null || fieldProps === void 0 ? void 0 : fieldProps.labelStyle) !== null && _b !== void 0 ? _b : "above";
|
|
30
|
-
// Call `initializeOptions` to prepend the `unset` option if the `unsetLabel` was provided.
|
|
31
|
-
const maybeOptions = (0, react_1.useMemo)(() => initializeOptions(options, unsetLabel), [options, unsetLabel]);
|
|
32
30
|
// Memoize the callback functions and handle the `unset` option if provided.
|
|
33
|
-
const getOptionLabel = (0, react_1.useCallback)((o) => (unsetLabel && o === exports.unsetOption ? unsetLabel :
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
[props.getOptionValue, unsetLabel]);
|
|
41
|
-
const getOptionMenuLabel = (0, react_1.useCallback)((o) => props.getOptionMenuLabel
|
|
42
|
-
? props.getOptionMenuLabel(o, Boolean(unsetLabel) && o === exports.unsetOption)
|
|
43
|
-
: getOptionLabel(o),
|
|
44
|
-
// TODO: validate this eslint-disable. It was automatically ignored as part of https://app.shortcut.com/homebound-team/story/40033/enable-react-hooks-exhaustive-deps-for-react-projects
|
|
31
|
+
const getOptionLabel = (0, react_1.useCallback)((o) => (unsetLabel && o === exports.unsetOption ? unsetLabel : propOptionLabel(o)), [propOptionLabel, unsetLabel]);
|
|
32
|
+
const getOptionValue = (0, react_1.useCallback)((o) => (unsetLabel && o === exports.unsetOption ? undefined : propOptionValue(o)), [propOptionValue, unsetLabel]);
|
|
33
|
+
const getOptionMenuLabel = (0, react_1.useCallback)((o) => propOptionMenuLabel ? propOptionMenuLabel(o, Boolean(unsetLabel) && o === exports.unsetOption) : getOptionLabel(o), [propOptionMenuLabel, unsetLabel, getOptionLabel]);
|
|
34
|
+
// Call `initializeOptions` to prepend the `unset` option if the `unsetLabel` was provided.
|
|
35
|
+
const options = (0, react_1.useMemo)(() => initializeOptions(propOptions, getOptionValue, unsetLabel),
|
|
36
|
+
// If the caller is using { current, load, options }, memoize on only `current` and `options` values.
|
|
37
|
+
// ...and don't bother on memoizing on getOptionValue b/c it's basically always a lambda
|
|
45
38
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
46
|
-
[
|
|
39
|
+
Array.isArray(propOptions) ? [propOptions, unsetLabel] : [propOptions.current, propOptions.options, unsetLabel]);
|
|
47
40
|
const { contains } = (0, react_aria_1.useFilter)({ sensitivity: "base" });
|
|
48
41
|
const isDisabled = !!disabled;
|
|
49
42
|
const isReadOnly = !!readOnly;
|
|
43
|
+
// Do a one-time initialize of fieldState
|
|
50
44
|
const [fieldState, setFieldState] = (0, react_1.useState)(() => {
|
|
51
45
|
var _a;
|
|
52
|
-
const
|
|
53
|
-
const selectedOptions = initOptions.filter((o) => values.includes(getOptionValue(o)));
|
|
46
|
+
const selectedOptions = options.filter((o) => values.includes(getOptionValue(o)));
|
|
54
47
|
return {
|
|
55
48
|
selectedKeys: (_a = selectedOptions === null || selectedOptions === void 0 ? void 0 : selectedOptions.map((o) => (0, Value_1.valueToKey)(getOptionValue(o)))) !== null && _a !== void 0 ? _a : [],
|
|
56
|
-
inputValue: getInputValue(
|
|
57
|
-
filteredOptions:
|
|
58
|
-
allOptions:
|
|
59
|
-
selectedOptions
|
|
49
|
+
inputValue: getInputValue(options.filter((o) => values === null || values === void 0 ? void 0 : values.includes(getOptionValue(o))), getOptionLabel, multiselect, nothingSelectedText),
|
|
50
|
+
filteredOptions: options,
|
|
51
|
+
allOptions: options,
|
|
52
|
+
selectedOptions,
|
|
60
53
|
optionsLoading: false,
|
|
61
54
|
};
|
|
62
55
|
});
|
|
@@ -123,17 +116,10 @@ function ComboBoxBase(props) {
|
|
|
123
116
|
}
|
|
124
117
|
}
|
|
125
118
|
async function maybeInitLoad() {
|
|
126
|
-
if (!Array.isArray(
|
|
119
|
+
if (!Array.isArray(propOptions)) {
|
|
127
120
|
setFieldState((prevState) => ({ ...prevState, optionsLoading: true }));
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const options = !unsetLabel ? loadedOptions : getOptionsWithUnset(unsetLabel, loadedOptions);
|
|
131
|
-
setFieldState((prevState) => ({
|
|
132
|
-
...prevState,
|
|
133
|
-
filteredOptions: options,
|
|
134
|
-
allOptions: options,
|
|
135
|
-
optionsLoading: false,
|
|
136
|
-
}));
|
|
121
|
+
await propOptions.load();
|
|
122
|
+
setFieldState((prevState) => ({ ...prevState, optionsLoading: false }));
|
|
137
123
|
}
|
|
138
124
|
}
|
|
139
125
|
const firstOpen = (0, react_1.useRef)(true);
|
|
@@ -216,37 +202,29 @@ function ComboBoxBase(props) {
|
|
|
216
202
|
// TODO: validate this eslint-disable. It was automatically ignored as part of https://app.shortcut.com/homebound-team/story/40033/enable-react-hooks-exhaustive-deps-for-react-projects
|
|
217
203
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
218
204
|
[values]);
|
|
205
|
+
// Re-sync fieldState.allOptions
|
|
219
206
|
(0, react_1.useEffect)(() => {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
? getOptionLabel(selectedOptions[0])
|
|
237
|
-
: multiselect && selectedOptions.length === 0
|
|
238
|
-
? nothingSelectedText
|
|
239
|
-
: "",
|
|
240
|
-
selectedOptions: selectedOptions,
|
|
241
|
-
filteredOptions: maybeUpdatedOptions,
|
|
242
|
-
allOptions: maybeUpdatedOptions,
|
|
243
|
-
};
|
|
244
|
-
});
|
|
245
|
-
}
|
|
207
|
+
setFieldState((prevState) => {
|
|
208
|
+
var _a;
|
|
209
|
+
const selectedOptions = options.filter((o) => values === null || values === void 0 ? void 0 : values.includes(getOptionValue(o)));
|
|
210
|
+
return {
|
|
211
|
+
...prevState,
|
|
212
|
+
selectedKeys: (_a = selectedOptions === null || selectedOptions === void 0 ? void 0 : selectedOptions.map((o) => (0, Value_1.valueToKey)(getOptionValue(o)))) !== null && _a !== void 0 ? _a : [],
|
|
213
|
+
inputValue: selectedOptions.length === 1
|
|
214
|
+
? getOptionLabel(selectedOptions[0])
|
|
215
|
+
: multiselect && selectedOptions.length === 0
|
|
216
|
+
? nothingSelectedText
|
|
217
|
+
: "",
|
|
218
|
+
selectedOptions: selectedOptions,
|
|
219
|
+
filteredOptions: options,
|
|
220
|
+
allOptions: options,
|
|
221
|
+
};
|
|
222
|
+
});
|
|
246
223
|
},
|
|
247
|
-
//
|
|
224
|
+
// We're primarily only re-setting `allOptions`, and so recalc selected as well, but we don't
|
|
225
|
+
// want to depend on values/etc., b/c we'll defer to their useEffects to update their state
|
|
248
226
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
249
|
-
[
|
|
227
|
+
[options]);
|
|
250
228
|
// For the most part, the returned props contain `aria-*` and `id` attributes for accessibility purposes.
|
|
251
229
|
const { buttonProps: triggerProps, inputProps, listBoxProps, labelProps, } = (0, react_aria_1.useComboBox)({
|
|
252
230
|
...comboBoxProps,
|
|
@@ -283,19 +261,33 @@ function getInputValue(selectedOptions, getOptionLabel, multiselect, nothingSele
|
|
|
283
261
|
? nothingSelectedText
|
|
284
262
|
: "";
|
|
285
263
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
264
|
+
/** Transforms/simplifies `optionsOrLoad` into just options, with unsetLabel maybe added. */
|
|
265
|
+
function initializeOptions(optionsOrLoad, getOptionValue, unsetLabel) {
|
|
266
|
+
const opts = [];
|
|
267
|
+
if (unsetLabel) {
|
|
268
|
+
opts.push(exports.unsetOption);
|
|
289
269
|
}
|
|
290
|
-
if (Array.isArray(
|
|
291
|
-
|
|
270
|
+
if (Array.isArray(optionsOrLoad)) {
|
|
271
|
+
opts.push(...optionsOrLoad);
|
|
292
272
|
}
|
|
293
|
-
|
|
273
|
+
else {
|
|
274
|
+
const { options, current } = optionsOrLoad;
|
|
275
|
+
if (options) {
|
|
276
|
+
opts.push(...options);
|
|
277
|
+
}
|
|
278
|
+
// Even if the SelectField has lazy-loaded options, make sure the current value is really in there
|
|
279
|
+
if (current) {
|
|
280
|
+
const value = getOptionValue(current);
|
|
281
|
+
const found = options && options.find((o) => getOptionValue(o) === value);
|
|
282
|
+
if (!found) {
|
|
283
|
+
opts.push(current);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return opts;
|
|
294
288
|
}
|
|
295
289
|
exports.initializeOptions = initializeOptions;
|
|
296
|
-
|
|
297
|
-
return [exports.unsetOption, ...options];
|
|
298
|
-
}
|
|
290
|
+
/** A marker option to automatically add an "Unset" option to the start of options. */
|
|
299
291
|
exports.unsetOption = {};
|
|
300
292
|
function disabledOptionToKeyedTuple(disabledOption) {
|
|
301
293
|
if (typeof disabledOption === "object" && disabledOption !== null) {
|
|
@@ -306,3 +298,6 @@ function disabledOptionToKeyedTuple(disabledOption) {
|
|
|
306
298
|
}
|
|
307
299
|
}
|
|
308
300
|
exports.disabledOptionToKeyedTuple = disabledOptionToKeyedTuple;
|
|
301
|
+
function asArray(arrayOrElement) {
|
|
302
|
+
return Array.isArray(arrayOrElement) ? arrayOrElement : arrayOrElement ? [arrayOrElement] : [];
|
|
303
|
+
}
|