@homebound/beam 2.321.0 → 2.322.0
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/inputs/internal/ComboBoxBase.js +46 -94
- package/dist/utils/rtl.d.ts +1 -1
- package/dist/utils/rtl.js +17 -4
- package/package.json +1 -1
|
@@ -12,7 +12,6 @@ const Css_1 = require("../../Css");
|
|
|
12
12
|
const ComboBoxInput_1 = require("./ComboBoxInput");
|
|
13
13
|
const ListBox_1 = require("./ListBox");
|
|
14
14
|
const Value_1 = require("../Value");
|
|
15
|
-
const utils_1 = require("../../utils");
|
|
16
15
|
/**
|
|
17
16
|
* Provides a non-native select/dropdown widget that allows the user to type to filter the options.
|
|
18
17
|
*
|
|
@@ -25,45 +24,49 @@ const utils_1 = require("../../utils");
|
|
|
25
24
|
function ComboBoxBase(props) {
|
|
26
25
|
var _a, _b, _c, _d;
|
|
27
26
|
const { fieldProps } = (0, PresentationContext_1.usePresentationContext)();
|
|
28
|
-
const { disabled, readOnly, onSelect, options: propOptions, multiselect = false, values
|
|
27
|
+
const { disabled, readOnly, onSelect, options: propOptions, multiselect = false, values: propValues, nothingSelectedText = "", contrast, disabledOptions, borderless, unsetLabel, getOptionLabel: propOptionLabel, getOptionValue: propOptionValue, getOptionMenuLabel: propOptionMenuLabel, ...otherProps } = props;
|
|
29
28
|
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
29
|
// Memoize the callback functions and handle the `unset` option if provided.
|
|
31
|
-
const getOptionLabel = (0, react_1.useCallback)((o) => (unsetLabel && o === exports.unsetOption ? unsetLabel : propOptionLabel(o)),
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
const getOptionLabel = (0, react_1.useCallback)((o) => (unsetLabel && o === exports.unsetOption ? unsetLabel : propOptionLabel(o)),
|
|
31
|
+
// propOptionLabel is basically always a lambda, so don't dep on it
|
|
32
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
33
|
+
[unsetLabel]);
|
|
34
|
+
const getOptionValue = (0, react_1.useCallback)((o) => (unsetLabel && o === exports.unsetOption ? undefined : propOptionValue(o)),
|
|
35
|
+
// propOptionValue is basically always a lambda, so don't dep on it
|
|
36
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
37
|
+
[unsetLabel]);
|
|
38
|
+
const getOptionMenuLabel = (0, react_1.useCallback)((o) => propOptionMenuLabel ? propOptionMenuLabel(o, Boolean(unsetLabel) && o === exports.unsetOption) : getOptionLabel(o),
|
|
39
|
+
// propOptionMenuLabel is basically always a lambda, so don't dep on it
|
|
40
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
41
|
+
[unsetLabel, getOptionLabel]);
|
|
34
42
|
// Call `initializeOptions` to prepend the `unset` option if the `unsetLabel` was provided.
|
|
35
43
|
const options = (0, react_1.useMemo)(() => initializeOptions(propOptions, getOptionValue, unsetLabel),
|
|
36
44
|
// If the caller is using { current, load, options }, memoize on only `current` and `options` values.
|
|
37
45
|
// ...and don't bother on memoizing on getOptionValue b/c it's basically always a lambda
|
|
38
46
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
39
47
|
Array.isArray(propOptions) ? [propOptions, unsetLabel] : [propOptions.current, propOptions.options, unsetLabel]);
|
|
48
|
+
const values = (0, react_1.useMemo)(() => propValues !== null && propValues !== void 0 ? propValues : [], [propValues]);
|
|
49
|
+
const selectedOptions = (0, react_1.useMemo)(() => {
|
|
50
|
+
return options.filter((o) => values.includes(getOptionValue(o)));
|
|
51
|
+
}, [options, values, getOptionValue]);
|
|
40
52
|
const { contains } = (0, react_aria_1.useFilter)({ sensitivity: "base" });
|
|
41
53
|
const isDisabled = !!disabled;
|
|
42
54
|
const isReadOnly = !!readOnly;
|
|
43
55
|
// Do a one-time initialize of fieldState
|
|
44
56
|
const [fieldState, setFieldState] = (0, react_1.useState)(() => {
|
|
45
|
-
var _a;
|
|
46
|
-
const selectedOptions = options.filter((o) => values.includes(getOptionValue(o)));
|
|
47
57
|
return {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
filteredOptions: options,
|
|
51
|
-
allOptions: options,
|
|
52
|
-
selectedOptions,
|
|
58
|
+
inputValue: getInputValue(selectedOptions, getOptionLabel, multiselect, nothingSelectedText),
|
|
59
|
+
searchValue: undefined,
|
|
53
60
|
optionsLoading: false,
|
|
54
61
|
};
|
|
55
62
|
});
|
|
63
|
+
const { searchValue } = fieldState;
|
|
64
|
+
const filteredOptions = (0, react_1.useMemo)(() => {
|
|
65
|
+
return !searchValue ? options : options.filter((o) => contains(getOptionLabel(o), searchValue));
|
|
66
|
+
}, [options, searchValue, getOptionLabel, contains]);
|
|
56
67
|
/** Resets field's input value and filtered options list for cases where the user exits the field without making changes (on Escape, or onBlur) */
|
|
57
68
|
function resetField() {
|
|
58
|
-
|
|
59
|
-
// Conditionally reset the value if the current inputValue doesn't match that of the passed in value, or we filtered the list
|
|
60
|
-
if (inputValue !== fieldState.inputValue || fieldState.filteredOptions.length !== fieldState.allOptions.length) {
|
|
61
|
-
setFieldState((prevState) => ({
|
|
62
|
-
...prevState,
|
|
63
|
-
inputValue,
|
|
64
|
-
filteredOptions: prevState.allOptions,
|
|
65
|
-
}));
|
|
66
|
-
}
|
|
69
|
+
setFieldState((prevState) => ({ ...prevState, searchValue: undefined }));
|
|
67
70
|
}
|
|
68
71
|
function onSelectionChange(keys) {
|
|
69
72
|
// We don't currently handle the "all" case
|
|
@@ -76,30 +79,11 @@ function ComboBoxBase(props) {
|
|
|
76
79
|
const selectionChanged = !(keys.size === state.selectionManager.selectedKeys.size &&
|
|
77
80
|
[...keys].every((value) => state.selectionManager.selectedKeys.has(value)));
|
|
78
81
|
if (multiselect && keys.size === 0) {
|
|
79
|
-
setFieldState({
|
|
80
|
-
...fieldState,
|
|
81
|
-
inputValue: state.isOpen ? "" : nothingSelectedText,
|
|
82
|
-
selectedKeys: [],
|
|
83
|
-
selectedOptions: [],
|
|
84
|
-
});
|
|
85
82
|
selectionChanged && onSelect([], []);
|
|
86
83
|
return;
|
|
87
84
|
}
|
|
88
85
|
const selectedKeys = [...keys.values()];
|
|
89
|
-
const selectedOptions =
|
|
90
|
-
const firstSelectedOption = selectedOptions[0];
|
|
91
|
-
setFieldState((prevState) => ({
|
|
92
|
-
...prevState,
|
|
93
|
-
// If menu is open then reset inputValue to "". Otherwise set inputValue depending on number of options selected.
|
|
94
|
-
inputValue: multiselect && (state.isOpen || selectedKeys.length > 1)
|
|
95
|
-
? ""
|
|
96
|
-
: firstSelectedOption
|
|
97
|
-
? getOptionLabel(firstSelectedOption)
|
|
98
|
-
: "",
|
|
99
|
-
selectedKeys,
|
|
100
|
-
selectedOptions,
|
|
101
|
-
filteredOptions: fieldState.allOptions,
|
|
102
|
-
}));
|
|
86
|
+
const selectedOptions = options.filter((o) => selectedKeys.includes((0, Value_1.valueToKey)(getOptionValue(o))));
|
|
103
87
|
selectionChanged && onSelect(selectedKeys.map(Value_1.keyToValue), selectedOptions);
|
|
104
88
|
if (!multiselect) {
|
|
105
89
|
// Close menu upon selection change only for Single selection mode
|
|
@@ -108,11 +92,7 @@ function ComboBoxBase(props) {
|
|
|
108
92
|
}
|
|
109
93
|
function onInputChange(value) {
|
|
110
94
|
if (value !== fieldState.inputValue) {
|
|
111
|
-
setFieldState((prevState) => ({
|
|
112
|
-
...prevState,
|
|
113
|
-
inputValue: value,
|
|
114
|
-
filteredOptions: fieldState.allOptions.filter((o) => contains(getOptionLabel(o), value)),
|
|
115
|
-
}));
|
|
95
|
+
setFieldState((prevState) => ({ ...prevState, inputValue: value, searchValue: value }));
|
|
116
96
|
}
|
|
117
97
|
}
|
|
118
98
|
async function maybeInitLoad() {
|
|
@@ -147,7 +127,7 @@ function ComboBoxBase(props) {
|
|
|
147
127
|
...otherProps,
|
|
148
128
|
disabledKeys: Object.keys(disabledOptionsWithReasons),
|
|
149
129
|
inputValue: fieldState.inputValue,
|
|
150
|
-
items:
|
|
130
|
+
items: filteredOptions,
|
|
151
131
|
isDisabled,
|
|
152
132
|
isReadOnly,
|
|
153
133
|
onInputChange,
|
|
@@ -172,59 +152,34 @@ function ComboBoxBase(props) {
|
|
|
172
152
|
}
|
|
173
153
|
},
|
|
174
154
|
});
|
|
155
|
+
const selectedKeys = (0, react_1.useMemo)(() => {
|
|
156
|
+
return selectedOptions.map((o) => (0, Value_1.valueToKey)(getOptionValue(o)));
|
|
157
|
+
}, [selectedOptions, getOptionValue]);
|
|
175
158
|
// @ts-ignore - `selectionManager.state` exists, but not according to the types
|
|
176
159
|
state.selectionManager.state = (0, react_stately_1.useMultipleSelectionState)({
|
|
177
160
|
selectionMode: multiselect ? "multiple" : "single",
|
|
178
161
|
// Do not allow an empty selection if single select mode
|
|
179
162
|
disallowEmptySelection: !multiselect,
|
|
180
|
-
selectedKeys
|
|
163
|
+
selectedKeys,
|
|
181
164
|
onSelectionChange,
|
|
182
165
|
});
|
|
183
|
-
//
|
|
166
|
+
// Reset inputValue when closed or selected changes
|
|
184
167
|
(0, react_1.useEffect)(() => {
|
|
185
|
-
if (
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
inputValue: selectedOptions.length === 1
|
|
193
|
-
? getOptionLabel(selectedOptions[0])
|
|
194
|
-
: multiselect && selectedOptions.length === 0
|
|
195
|
-
? nothingSelectedText
|
|
196
|
-
: "",
|
|
197
|
-
selectedOptions: selectedOptions,
|
|
198
|
-
};
|
|
199
|
-
});
|
|
168
|
+
if (state.isOpen && multiselect) {
|
|
169
|
+
// While the multiselect is open, let the user keep typing
|
|
170
|
+
setFieldState((prevState) => ({
|
|
171
|
+
...prevState,
|
|
172
|
+
inputValue: "",
|
|
173
|
+
searchValue: "",
|
|
174
|
+
}));
|
|
200
175
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
204
|
-
[values]);
|
|
205
|
-
// Re-sync fieldState.allOptions
|
|
206
|
-
(0, react_1.useEffect)(() => {
|
|
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 {
|
|
176
|
+
else {
|
|
177
|
+
setFieldState((prevState) => ({
|
|
211
178
|
...prevState,
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
? nothingSelectedText
|
|
217
|
-
: "",
|
|
218
|
-
selectedOptions: selectedOptions,
|
|
219
|
-
filteredOptions: options,
|
|
220
|
-
allOptions: options,
|
|
221
|
-
};
|
|
222
|
-
});
|
|
223
|
-
},
|
|
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
|
|
226
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
227
|
-
[options]);
|
|
179
|
+
inputValue: getInputValue(selectedOptions, getOptionLabel, multiselect, nothingSelectedText),
|
|
180
|
+
}));
|
|
181
|
+
}
|
|
182
|
+
}, [state.isOpen, selectedOptions, getOptionLabel, multiselect, nothingSelectedText]);
|
|
228
183
|
// For the most part, the returned props contain `aria-*` and `id` attributes for accessibility purposes.
|
|
229
184
|
const { buttonProps: triggerProps, inputProps, listBoxProps, labelProps, } = (0, react_aria_1.useComboBox)({
|
|
230
185
|
...comboBoxProps,
|
|
@@ -251,7 +206,7 @@ function ComboBoxBase(props) {
|
|
|
251
206
|
// Ensures the menu never gets too small.
|
|
252
207
|
minWidth: 200,
|
|
253
208
|
};
|
|
254
|
-
return ((0, jsx_runtime_1.jsxs)("div", { css: Css_1.Css.df.fdc.w100.maxw((0, Css_1.px)(550)).if(labelStyle === "left").maxw100.$, ref: comboBoxRef, children: [(0, jsx_runtime_1.jsx)(ComboBoxInput_1.ComboBoxInput, { ...otherProps, buttonProps: buttonProps, buttonRef: triggerRef, inputProps: inputProps, inputRef: inputRef, inputWrapRef: inputWrapRef, listBoxRef: listBoxRef, state: state, labelProps: labelProps, selectedOptions:
|
|
209
|
+
return ((0, jsx_runtime_1.jsxs)("div", { css: Css_1.Css.df.fdc.w100.maxw((0, Css_1.px)(550)).if(labelStyle === "left").maxw100.$, ref: comboBoxRef, children: [(0, jsx_runtime_1.jsx)(ComboBoxInput_1.ComboBoxInput, { ...otherProps, buttonProps: buttonProps, buttonRef: triggerRef, inputProps: inputProps, inputRef: inputRef, inputWrapRef: inputWrapRef, listBoxRef: listBoxRef, state: state, labelProps: labelProps, selectedOptions: selectedOptions, getOptionValue: getOptionValue, getOptionLabel: getOptionLabel, contrast: contrast, nothingSelectedText: nothingSelectedText, borderless: borderless, tooltip: (0, components_1.resolveTooltip)(disabled, undefined, readOnly), resetField: resetField }), state.isOpen && ((0, jsx_runtime_1.jsx)(internal_1.Popover, { triggerRef: triggerRef, popoverRef: popoverRef, positionProps: positionProps, onClose: () => state.close(), isOpen: state.isOpen, minWidth: 200, children: (0, jsx_runtime_1.jsx)(ListBox_1.ListBox, { ...listBoxProps, positionProps: positionProps, state: state, listBoxRef: listBoxRef, selectedOptions: selectedOptions, getOptionLabel: getOptionLabel, getOptionValue: (o) => (0, Value_1.valueToKey)(getOptionValue(o)), contrast: contrast, horizontalLayout: labelStyle === "left", loading: fieldState.optionsLoading, disabledOptionsWithReasons: disabledOptionsWithReasons }) }))] }));
|
|
255
210
|
}
|
|
256
211
|
exports.ComboBoxBase = ComboBoxBase;
|
|
257
212
|
function getInputValue(selectedOptions, getOptionLabel, multiselect, nothingSelectedText) {
|
|
@@ -298,6 +253,3 @@ function disabledOptionToKeyedTuple(disabledOption) {
|
|
|
298
253
|
}
|
|
299
254
|
}
|
|
300
255
|
exports.disabledOptionToKeyedTuple = disabledOptionToKeyedTuple;
|
|
301
|
-
function asArray(arrayOrElement) {
|
|
302
|
-
return Array.isArray(arrayOrElement) ? arrayOrElement : arrayOrElement ? [arrayOrElement] : [];
|
|
303
|
-
}
|
package/dist/utils/rtl.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export declare function rowAnd(r: RenderResult, rowNum: number, testId: string):
|
|
|
29
29
|
"
|
|
30
30
|
`);
|
|
31
31
|
* */
|
|
32
|
-
export declare function tableSnapshot(r: RenderResult): string;
|
|
32
|
+
export declare function tableSnapshot(r: RenderResult, columnNames?: string[]): string;
|
|
33
33
|
/** RTL wrapper for Beam's SuperDrawer/Modal context. */
|
|
34
34
|
export declare const withBeamRTL: Wrapper;
|
|
35
35
|
/**
|
package/dist/utils/rtl.js
CHANGED
|
@@ -76,17 +76,30 @@ exports.rowAnd = rowAnd;
|
|
|
76
76
|
"
|
|
77
77
|
`);
|
|
78
78
|
* */
|
|
79
|
-
function tableSnapshot(r) {
|
|
79
|
+
function tableSnapshot(r, columnNames = []) {
|
|
80
80
|
const tableEl = r.getByTestId("gridTable");
|
|
81
81
|
const dataRows = Array.from(tableEl.querySelectorAll("[data-gridrow]"));
|
|
82
82
|
const hasExpandableHeader = !!tableEl.querySelector(`[data-testid="expandableColumn"]`);
|
|
83
|
-
|
|
83
|
+
let tableDataAsStrings = dataRows.map((row) => {
|
|
84
84
|
return Array.from(row.childNodes).map(getTextFromTableCellNode);
|
|
85
85
|
});
|
|
86
|
-
|
|
86
|
+
// If the user wants a subset of columns, look for column names
|
|
87
|
+
if (columnNames.length > 0) {
|
|
88
|
+
const headerCells = tableDataAsStrings[0];
|
|
89
|
+
if (headerCells) {
|
|
90
|
+
const columnIndices = columnNames.map((name) => {
|
|
91
|
+
const i = headerCells.indexOf(name);
|
|
92
|
+
if (i === -1)
|
|
93
|
+
throw new Error(`Could not find header '${name}' in ${headerCells.join(", ")}`);
|
|
94
|
+
return i;
|
|
95
|
+
});
|
|
96
|
+
tableDataAsStrings = tableDataAsStrings.map((row) => columnIndices.map((index) => row[index]));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return toMarkupTableString(tableDataAsStrings, hasExpandableHeader);
|
|
87
100
|
}
|
|
88
101
|
exports.tableSnapshot = tableSnapshot;
|
|
89
|
-
function toMarkupTableString(
|
|
102
|
+
function toMarkupTableString(tableRows, hasExpandableHeader) {
|
|
90
103
|
// Find the largest width of each column to set a consistent width for each row
|
|
91
104
|
const columnWidths = tableRows.reduce((acc, row) => {
|
|
92
105
|
row.forEach((cell, columnIndex) => {
|