@homebound/beam 2.91.5 → 2.91.6
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/ChipSelectField.d.ts +6 -0
- package/dist/inputs/ChipSelectField.js +30 -21
- package/dist/inputs/internal/ListBox.d.ts +0 -1
- package/dist/inputs/internal/ListBox.js +27 -30
- package/dist/inputs/internal/ListBoxSection.d.ts +12 -0
- package/dist/inputs/internal/ListBoxSection.js +20 -0
- package/dist/inputs/internal/Option.d.ts +1 -0
- package/dist/inputs/internal/Option.js +20 -2
- package/dist/inputs/internal/SelectFieldBase.js +2 -3
- package/dist/inputs/internal/VirtualizedOptions.d.ts +11 -0
- package/dist/inputs/internal/VirtualizedOptions.js +38 -0
- package/dist/inputs/internal/constants.d.ts +2 -0
- package/dist/inputs/internal/constants.js +5 -0
- package/package.json +1 -1
|
@@ -24,4 +24,10 @@ declare type PersistentItem = {
|
|
|
24
24
|
};
|
|
25
25
|
export declare function isPersistentItem<T extends PersistentItem>(opt: any): opt is PersistentItem;
|
|
26
26
|
export declare function isPersistentKey(key: Key): boolean;
|
|
27
|
+
declare type ListBoxSection<O> = {
|
|
28
|
+
title: string;
|
|
29
|
+
options: O[];
|
|
30
|
+
isPersistent?: boolean;
|
|
31
|
+
};
|
|
32
|
+
export declare function isListBoxSection<O>(obj: O | ListBoxSection<O>): obj is ListBoxSection<O>;
|
|
27
33
|
export {};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isPersistentKey = exports.isPersistentItem = exports.persistentItemPrefix = exports.ChipSelectField = void 0;
|
|
3
|
+
exports.isListBoxSection = exports.isPersistentKey = exports.isPersistentItem = exports.persistentItemPrefix = exports.ChipSelectField = void 0;
|
|
4
4
|
const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
|
|
5
|
+
const change_case_1 = require("change-case");
|
|
5
6
|
const react_1 = require("react");
|
|
6
7
|
const react_aria_1 = require("react-aria");
|
|
7
8
|
const react_stately_1 = require("react-stately");
|
|
@@ -53,25 +54,28 @@ function ChipSelectField(props) {
|
|
|
53
54
|
const wrapperRef = (0, react_1.useRef)(null);
|
|
54
55
|
// Using `ListData` in order to dynamically update the items
|
|
55
56
|
const listData = (0, react_stately_1.useListData)({
|
|
56
|
-
initialItems:
|
|
57
|
+
initialItems: !onCreateNew
|
|
58
|
+
? options
|
|
59
|
+
: [
|
|
60
|
+
{ title: "Options", options },
|
|
61
|
+
{ title: "Actions", isPersistent: true, options: [createNewOpt] },
|
|
62
|
+
],
|
|
57
63
|
initialSelectedKeys: [(0, Value_1.valueToKey)(value)],
|
|
58
|
-
getKey: (item) => (
|
|
64
|
+
getKey: (item) => (isListBoxSection(item) ? item.title : getOptionValue(item)),
|
|
59
65
|
});
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
const label = isPersistent ? o.name : getOptionLabel(o);
|
|
74
|
-
return ((0, jsx_runtime_1.jsx)(react_stately_1.Item, Object.assign({ textValue: label }, { children: isPersistent ? (label) : ((0, jsx_runtime_1.jsx)("span", Object.assign({ css: { ...Css_1.Css.lineClamp1.breakAll.$, ...chipStyles }, title: label }, { children: label }), void 0)) }), value));
|
|
66
|
+
(0, react_1.useEffect)(() => listData.update("Options", { title: "Options", options }), [options]);
|
|
67
|
+
const selectChildren = listData.items.map((s) => {
|
|
68
|
+
if (isListBoxSection(s)) {
|
|
69
|
+
return ((0, jsx_runtime_1.jsx)(react_stately_1.Section, Object.assign({ title: s.title, items: s.options }, { children: (item) => {
|
|
70
|
+
if (isPersistentItem(item)) {
|
|
71
|
+
return ((0, jsx_runtime_1.jsx)(react_stately_1.Item, Object.assign({ textValue: item.name }, { children: item.name }), item.id));
|
|
72
|
+
}
|
|
73
|
+
const label = getOptionLabel(item);
|
|
74
|
+
return ((0, jsx_runtime_1.jsx)(react_stately_1.Item, Object.assign({ textValue: label }, { children: (0, jsx_runtime_1.jsx)("span", Object.assign({ css: { ...Css_1.Css.lineClamp1.breakAll.$, ...chipStyles }, title: label }, { children: label }), void 0) }), getOptionValue(item)));
|
|
75
|
+
} }), (0, change_case_1.camelCase)(s.title)));
|
|
76
|
+
}
|
|
77
|
+
const label = getOptionLabel(s);
|
|
78
|
+
return ((0, jsx_runtime_1.jsx)(react_stately_1.Item, Object.assign({ textValue: label }, { children: (0, jsx_runtime_1.jsx)("span", Object.assign({ css: { ...Css_1.Css.lineClamp1.breakAll.$, ...chipStyles }, title: label }, { children: label }), void 0) }), getOptionValue(s)));
|
|
75
79
|
});
|
|
76
80
|
const selectHookProps = {
|
|
77
81
|
label,
|
|
@@ -121,6 +125,7 @@ function ChipSelectField(props) {
|
|
|
121
125
|
isOpen: state.isOpen,
|
|
122
126
|
onClose: state.close,
|
|
123
127
|
placement: "bottom left",
|
|
128
|
+
offset: 8,
|
|
124
129
|
});
|
|
125
130
|
overlayProps.style = {
|
|
126
131
|
...overlayProps.style,
|
|
@@ -132,10 +137,10 @@ function ChipSelectField(props) {
|
|
|
132
137
|
const [showInput, setShowInput] = (0, react_1.useState)(false);
|
|
133
138
|
const [inputValue, setInputValue] = (0, react_1.useState)("Add new");
|
|
134
139
|
const removeCreateNewField = (0, react_1.useCallback)(() => {
|
|
135
|
-
var _a;
|
|
136
140
|
setShowInput(false);
|
|
137
141
|
setInputValue("Add new");
|
|
138
|
-
|
|
142
|
+
// Trigger onBlur to initiate any auto-saving behavior.
|
|
143
|
+
(0, utils_1.maybeCall)(onBlur);
|
|
139
144
|
}, [setShowInput, setInputValue]);
|
|
140
145
|
const field = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [showInput && onCreateNew && ((0, jsx_runtime_1.jsx)(ChipTextField_1.ChipTextField, Object.assign({ autoFocus: true, label: "Add new", value: inputValue, onChange: setInputValue, onEnter: async () => {
|
|
141
146
|
await onCreateNew(inputValue);
|
|
@@ -157,7 +162,7 @@ function ChipSelectField(props) {
|
|
|
157
162
|
onSelect(undefined, undefined);
|
|
158
163
|
(0, utils_1.maybeCall)(onBlur);
|
|
159
164
|
setIsClearFocused(false);
|
|
160
|
-
}, "aria-label": "Remove" }, tid.clearButton, { children: (0, jsx_runtime_1.jsx)(components_1.Icon, { icon: "x", inc: typeScale === "xs" ? 2 : undefined }, void 0) }), void 0))] }), void 0), state.isOpen && ((0, jsx_runtime_1.jsx)(internal_1.Popover, Object.assign({ triggerRef: buttonRef, popoverRef: popoverRef, positionProps: overlayProps, onClose: state.close, isOpen: state.isOpen, shouldCloseOnBlur: true }, { children: (0, jsx_runtime_1.jsx)(ListBox_1.ListBox, Object.assign({}, menuProps, { listBoxRef: listBoxRef, state: state, getOptionLabel: getOptionLabel, getOptionValue: getOptionValue, positionProps: overlayProps
|
|
165
|
+
}, "aria-label": "Remove" }, tid.clearButton, { children: (0, jsx_runtime_1.jsx)(components_1.Icon, { icon: "x", inc: typeScale === "xs" ? 2 : undefined }, void 0) }), void 0))] }), void 0), state.isOpen && ((0, jsx_runtime_1.jsx)(internal_1.Popover, Object.assign({ triggerRef: buttonRef, popoverRef: popoverRef, positionProps: overlayProps, onClose: state.close, isOpen: state.isOpen, shouldCloseOnBlur: true }, { children: (0, jsx_runtime_1.jsx)(ListBox_1.ListBox, Object.assign({}, menuProps, { listBoxRef: listBoxRef, state: state, getOptionLabel: getOptionLabel, getOptionValue: getOptionValue, positionProps: overlayProps }), void 0) }), void 0))] }, void 0));
|
|
161
166
|
const tooltipText = selectHookProps.isDisabled && typeof disabled !== "boolean" ? disabled : undefined;
|
|
162
167
|
return tooltipText ? ((0, jsx_runtime_1.jsx)(components_1.Tooltip, Object.assign({ title: tooltipText, placement: "top" }, { children: field }), void 0)) : (field);
|
|
163
168
|
}
|
|
@@ -175,3 +180,7 @@ function isPersistentKey(key) {
|
|
|
175
180
|
return typeof key === "string" && key.startsWith(exports.persistentItemPrefix);
|
|
176
181
|
}
|
|
177
182
|
exports.isPersistentKey = isPersistentKey;
|
|
183
|
+
function isListBoxSection(obj) {
|
|
184
|
+
return typeof obj === "object" && "options" in obj;
|
|
185
|
+
}
|
|
186
|
+
exports.isListBoxSection = isListBoxSection;
|
|
@@ -8,7 +8,6 @@ interface ListBoxProps<O, V extends Key> {
|
|
|
8
8
|
getOptionValue: (opt: O) => V;
|
|
9
9
|
contrast?: boolean;
|
|
10
10
|
positionProps: React.HTMLAttributes<Element>;
|
|
11
|
-
positionOffset?: number;
|
|
12
11
|
}
|
|
13
12
|
/** A ListBox is an internal component used by SelectField and MultiSelectField to display the list of options */
|
|
14
13
|
export declare function ListBox<O, V extends Key>(props: ListBoxProps<O, V>): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
@@ -4,45 +4,40 @@ exports.ListBox = void 0;
|
|
|
4
4
|
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
|
-
const react_virtuoso_1 = require("react-virtuoso");
|
|
8
7
|
const ToggleChip_1 = require("../../components/ToggleChip");
|
|
9
8
|
const Css_1 = require("../../Css");
|
|
10
|
-
const
|
|
9
|
+
const constants_1 = require("./constants");
|
|
10
|
+
const ListBoxSection_1 = require("./ListBoxSection");
|
|
11
|
+
const VirtualizedOptions_1 = require("./VirtualizedOptions");
|
|
11
12
|
/** A ListBox is an internal component used by SelectField and MultiSelectField to display the list of options */
|
|
12
13
|
function ListBox(props) {
|
|
13
14
|
var _a;
|
|
14
|
-
const { state, listBoxRef, selectedOptions = [], getOptionLabel, getOptionValue, contrast = false, positionProps,
|
|
15
|
-
const { listBoxProps } = (0, react_aria_1.useListBox)({ disallowEmptySelection: true,
|
|
15
|
+
const { state, listBoxRef, selectedOptions = [], getOptionLabel, getOptionValue, contrast = false, positionProps, } = props;
|
|
16
|
+
const { listBoxProps } = (0, react_aria_1.useListBox)({ disallowEmptySelection: true, ...props }, state, listBoxRef);
|
|
16
17
|
const positionMaxHeight = (_a = positionProps.style) === null || _a === void 0 ? void 0 : _a.maxHeight;
|
|
17
|
-
// The
|
|
18
|
-
// If `maxHeight` is set use that, otherwise use `
|
|
19
|
-
//
|
|
20
|
-
const
|
|
21
|
-
|
|
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`).
|
|
19
|
+
// If `maxHeight` is set use that, otherwise use `maxPopoverHeight` as a default, per UX guidelines.
|
|
20
|
+
// (`positionMaxHeight` should always be set and defined as a number, but we need to do these type checks to make TS happy)
|
|
21
|
+
const popoverMaxHeight = positionMaxHeight && typeof positionMaxHeight === "number"
|
|
22
|
+
? Math.min(positionMaxHeight, maxPopoverHeight)
|
|
23
|
+
: maxPopoverHeight;
|
|
24
|
+
const [popoverHeight, setPopoverHeight] = (0, react_1.useState)(popoverMaxHeight);
|
|
22
25
|
const isMultiSelect = state.selectionManager.selectionMode === "multiple";
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}, [focusedItem]);
|
|
26
|
+
const firstItem = state.collection.at(0);
|
|
27
|
+
const hasSections = firstItem && firstItem.type === "section";
|
|
28
|
+
const onListHeightChange = (height) => {
|
|
29
|
+
// Using Math.min to choose between the smaller height, either the total height of the List (`height` arg), or the maximum height defined by the space allotted on screen or our hard coded max.
|
|
30
|
+
// If there are ListBoxSections, then we assume it is the persistent section with a single item and account for that height.
|
|
31
|
+
setPopoverHeight(Math.min(popoverMaxHeight, hasSections ? height + constants_1.persistentItemHeight + constants_1.sectionSeparatorHeight : height));
|
|
32
|
+
};
|
|
31
33
|
return ((0, jsx_runtime_1.jsxs)("div", Object.assign({ css: {
|
|
32
|
-
...Css_1.Css.bgWhite.br4.w100.bshBasic.
|
|
34
|
+
...Css_1.Css.bgWhite.br4.w100.bshBasic.if(contrast).bgGray700.$,
|
|
33
35
|
"&:hover": Css_1.Css.bshHover.$,
|
|
34
|
-
}, ref: listBoxRef }, listBoxProps, { children: [isMultiSelect && state.selectionManager.selectedKeys.size > 0 && ((0, jsx_runtime_1.jsx)("ul", Object.assign({ css: Css_1.Css.listReset.pt2.pl2.pb1.pr1.df.bb.bGray200.add("flexWrap", "wrap").$ }, { children: selectedOptions.map((o) => ((0, jsx_runtime_1.jsx)(ListBoxChip, { state: state, option: o, getOptionValue: getOptionValue, getOptionLabel: getOptionLabel, disabled: state.disabledKeys.has(getOptionValue(o)) }, getOptionValue(o)))) }), void 0)), (0, jsx_runtime_1.jsx)("ul", Object.assign({ css: Css_1.Css.listReset.hPx(
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
// MapIterator doesn't have at/index lookup so make a copy
|
|
40
|
-
const keys = [...state.collection.getKeys()];
|
|
41
|
-
const item = state.collection.getItem(keys[idx]);
|
|
42
|
-
if (item) {
|
|
43
|
-
return (0, jsx_runtime_1.jsx)(Option_1.Option, { item: item, state: state, contrast: contrast }, item.key);
|
|
44
|
-
}
|
|
45
|
-
} }, void 0) }), void 0)] }), void 0));
|
|
36
|
+
}, ref: listBoxRef }, listBoxProps, { children: [isMultiSelect && state.selectionManager.selectedKeys.size > 0 && ((0, jsx_runtime_1.jsx)("ul", Object.assign({ css: Css_1.Css.listReset.pt2.pl2.pb1.pr1.df.bb.bGray200.add("flexWrap", "wrap").$ }, { children: selectedOptions.map((o) => ((0, jsx_runtime_1.jsx)(ListBoxChip, { state: state, option: o, getOptionValue: getOptionValue, getOptionLabel: getOptionLabel, disabled: state.disabledKeys.has(getOptionValue(o)) }, getOptionValue(o)))) }), void 0)), (0, jsx_runtime_1.jsx)("ul", Object.assign({ css: Css_1.Css.listReset.hPx(popoverHeight).$ }, { children: hasSections ? ([...state.collection].map((section) => ((0, jsx_runtime_1.jsx)(ListBoxSection_1.ListBoxSection, { section: section, state: state, contrast: contrast, onListHeightChange: onListHeightChange, popoverHeight: popoverHeight,
|
|
37
|
+
// Only scroll on focus if using VirtualFocus (used for ComboBoxState (SelectField), but not SelectState (ChipSelectField))
|
|
38
|
+
scrollOnFocus: props.shouldUseVirtualFocus }, section.key)))) : ((0, jsx_runtime_1.jsx)(VirtualizedOptions_1.VirtualizedOptions, { state: state, items: [...state.collection], onListHeightChange: onListHeightChange, contrast: contrast,
|
|
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));
|
|
46
41
|
}
|
|
47
42
|
exports.ListBox = ListBox;
|
|
48
43
|
/** Chip used to display selections within ListBox when using the MultiSelectField */
|
|
@@ -52,3 +47,5 @@ function ListBoxChip(props) {
|
|
|
52
47
|
state.selectionManager.toggleSelection(String(getOptionValue(option)));
|
|
53
48
|
}, disabled: disabled }, void 0) }), void 0));
|
|
54
49
|
}
|
|
50
|
+
// UX specified maximum height for a ListBox (in pixels)
|
|
51
|
+
const maxPopoverHeight = 512;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Node } from "@react-types/shared";
|
|
2
|
+
import { SelectState } from "react-stately";
|
|
3
|
+
interface ListBoxSectionProps<O> {
|
|
4
|
+
section: Node<O>;
|
|
5
|
+
state: SelectState<O>;
|
|
6
|
+
contrast: boolean;
|
|
7
|
+
onListHeightChange: (height: number) => void;
|
|
8
|
+
popoverHeight: number;
|
|
9
|
+
scrollOnFocus?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function ListBoxSection<O>(props: ListBoxSectionProps<O>): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ListBoxSection = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
|
|
5
|
+
const react_aria_1 = require("react-aria");
|
|
6
|
+
const Css_1 = require("../../Css");
|
|
7
|
+
const constants_1 = require("./constants");
|
|
8
|
+
const Option_1 = require("./Option");
|
|
9
|
+
const VirtualizedOptions_1 = require("./VirtualizedOptions");
|
|
10
|
+
// Creates a section of options within a ListBox.
|
|
11
|
+
// Currently only expects two possible sections; 1. The list of options, and 2. A persistent action (in that order).
|
|
12
|
+
function ListBoxSection(props) {
|
|
13
|
+
const { section, state, contrast, onListHeightChange, popoverHeight, scrollOnFocus } = props;
|
|
14
|
+
const { itemProps, groupProps } = (0, react_aria_1.useListBoxSection)(section);
|
|
15
|
+
const { separatorProps } = (0, react_aria_1.useSeparator)({ elementType: "li" });
|
|
16
|
+
const isPersistentSection = section.key !== state.collection.getFirstKey();
|
|
17
|
+
const childNodes = [...section.childNodes];
|
|
18
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [isPersistentSection && (0, jsx_runtime_1.jsx)("li", Object.assign({}, separatorProps, { css: Css_1.Css.bt.bGray200.$ }), void 0), (0, jsx_runtime_1.jsx)("li", Object.assign({}, itemProps, { css: Css_1.Css.if(!isPersistentSection).overflowAuto.$ }, { children: (0, jsx_runtime_1.jsx)("ul", Object.assign({ css: Css_1.Css.listReset.if(!isPersistentSection).hPx(popoverHeight - constants_1.sectionSeparatorHeight - constants_1.persistentItemHeight).$ }, groupProps, { children: isPersistentSection ? (childNodes.map((item) => (0, jsx_runtime_1.jsx)(Option_1.Option, { item: item, state: state, contrast: contrast }, item.key))) : ((0, jsx_runtime_1.jsx)(VirtualizedOptions_1.VirtualizedOptions, { state: state, items: childNodes, onListHeightChange: onListHeightChange, contrast: contrast, scrollOnFocus: scrollOnFocus }, void 0)) }), void 0) }), void 0)] }, void 0));
|
|
19
|
+
}
|
|
20
|
+
exports.ListBoxSection = ListBoxSection;
|
|
@@ -4,6 +4,7 @@ interface OptionProps<O> {
|
|
|
4
4
|
item: Node<O>;
|
|
5
5
|
state: ListState<O> | TreeState<O>;
|
|
6
6
|
contrast?: boolean;
|
|
7
|
+
scrollToIndex?: (index: number) => void;
|
|
7
8
|
}
|
|
8
9
|
/** Represents a single option within a ListBox - used by SelectField and MultiSelectField */
|
|
9
10
|
export declare function Option<O>(props: OptionProps<O>): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
@@ -9,7 +9,7 @@ const Css_1 = require("../../Css");
|
|
|
9
9
|
const ChipSelectField_1 = require("../ChipSelectField");
|
|
10
10
|
/** Represents a single option within a ListBox - used by SelectField and MultiSelectField */
|
|
11
11
|
function Option(props) {
|
|
12
|
-
const { item, state, contrast = false } = props;
|
|
12
|
+
const { item, state, contrast = false, scrollToIndex } = props;
|
|
13
13
|
const ref = (0, react_1.useRef)(null);
|
|
14
14
|
const { hoverProps, isHovered } = (0, react_aria_1.useHover)({});
|
|
15
15
|
const themeStyles = {
|
|
@@ -21,7 +21,25 @@ function Option(props) {
|
|
|
21
21
|
// Get props for the option element.
|
|
22
22
|
// Prevent options from receiving browser focus via shouldUseVirtualFocus.
|
|
23
23
|
const { optionProps, isDisabled, isFocused, isSelected } = (0, react_aria_1.useOption)({ key: item.key, shouldSelectOnPressUp: true, shouldFocusOnHover: false }, state, ref);
|
|
24
|
-
|
|
24
|
+
// Additional onKeyDown logic to ensure the the virtualized list (in <VirtualizedOptions />) scrolls to keep the "focused" option in view
|
|
25
|
+
const onKeyDown = (0, react_1.useCallback)((e) => {
|
|
26
|
+
if (!scrollToIndex || !(e.key === "ArrowDown" || e.key === "ArrowUp")) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const toKey = e.key === "ArrowDown" ? item.nextKey : item.prevKey;
|
|
30
|
+
if (!toKey) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const toItem = state.collection.getItem(toKey);
|
|
34
|
+
// Only scroll the "options" (`state.collection` is a flat list of sections and items - we want to avoid scrolling to a "section" as it is not shown in the UI)
|
|
35
|
+
if (toItem &&
|
|
36
|
+
// Ensure we are only ever scrolling to an "option".
|
|
37
|
+
(toItem.parentKey === "options" || (!toItem.parentKey && toItem.type === "item")) &&
|
|
38
|
+
toItem.index !== undefined) {
|
|
39
|
+
scrollToIndex(toItem.index);
|
|
40
|
+
}
|
|
41
|
+
}, [scrollToIndex, state]);
|
|
42
|
+
return ((0, jsx_runtime_1.jsxs)("li", Object.assign({}, (0, react_aria_1.mergeProps)(optionProps, hoverProps, { onKeyDown }), { ref: ref, css: {
|
|
25
43
|
...Css_1.Css.df.aic.jcsb.py1.px2.mh("42px").outline0.cursorPointer.sm.$,
|
|
26
44
|
// Assumes only one Persistent Item per list - will need to change to utilize Sections if that assumption is incorrect.
|
|
27
45
|
...((0, ChipSelectField_1.isPersistentKey)(item.key) ? Css_1.Css.bt.bGray200.$ : {}),
|
|
@@ -177,6 +177,7 @@ function SelectFieldBase(props) {
|
|
|
177
177
|
isOpen: state.isOpen,
|
|
178
178
|
onClose: state.close,
|
|
179
179
|
placement: "bottom left",
|
|
180
|
+
offset: borderless ? 8 : 4,
|
|
180
181
|
});
|
|
181
182
|
positionProps.style = {
|
|
182
183
|
...positionProps.style,
|
|
@@ -184,8 +185,6 @@ function SelectFieldBase(props) {
|
|
|
184
185
|
// Ensures the menu never gets too small.
|
|
185
186
|
minWidth: 200,
|
|
186
187
|
};
|
|
187
|
-
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, compact: compact, errorMsg: errorMsg, helperText: helperText, fieldDecoration: fieldDecoration, inputProps: inputProps, inputRef: inputRef, inputWrapRef: inputWrapRef, isDisabled: isDisabled, required: required, isReadOnly: isReadOnly, state: state, onBlur: onBlur, onFocus: onFocus, inlineLabel: inlineLabel, label: label, hideLabel: hideLabel, labelProps: labelProps, selectedOptions: fieldState.selectedOptions, getOptionValue: getOptionValue, getOptionLabel: getOptionLabel, sizeToContent: sizeToContent, contrast: contrast, nothingSelectedText: nothingSelectedText, borderless: borderless }), 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,
|
|
188
|
-
// If the field is set as `borderless`, then the focus state is done with a box-shadow and set further away from the input. If this happens then we want the ListBox to be positioned further away as well.
|
|
189
|
-
positionOffset: borderless ? 8 : undefined }), void 0) }), void 0))] }), void 0));
|
|
188
|
+
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, compact: compact, errorMsg: errorMsg, helperText: helperText, fieldDecoration: fieldDecoration, inputProps: inputProps, inputRef: inputRef, inputWrapRef: inputWrapRef, isDisabled: isDisabled, required: required, isReadOnly: isReadOnly, state: state, onBlur: onBlur, onFocus: onFocus, inlineLabel: inlineLabel, label: label, hideLabel: hideLabel, labelProps: labelProps, selectedOptions: fieldState.selectedOptions, getOptionValue: getOptionValue, getOptionLabel: getOptionLabel, sizeToContent: sizeToContent, contrast: contrast, nothingSelectedText: nothingSelectedText, borderless: borderless }), 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 }), void 0) }), void 0))] }), void 0));
|
|
190
189
|
}
|
|
191
190
|
exports.SelectFieldBase = SelectFieldBase;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Node } from "@react-types/shared";
|
|
2
|
+
import { SelectState } from "react-stately";
|
|
3
|
+
interface VirtualizedOptionsProps<O> {
|
|
4
|
+
state: SelectState<O>;
|
|
5
|
+
items: Node<O>[];
|
|
6
|
+
onListHeightChange: (height: number) => void;
|
|
7
|
+
contrast: boolean;
|
|
8
|
+
scrollOnFocus?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function VirtualizedOptions<O>(props: VirtualizedOptionsProps<O>): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.VirtualizedOptions = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const react_virtuoso_1 = require("react-virtuoso");
|
|
7
|
+
const Option_1 = require("./Option");
|
|
8
|
+
// Displays ListBox options in a virtualized container for performance reasons
|
|
9
|
+
function VirtualizedOptions(props) {
|
|
10
|
+
const { state, items, onListHeightChange, contrast, scrollOnFocus } = props;
|
|
11
|
+
const virtuosoRef = (0, react_1.useRef)(null);
|
|
12
|
+
const focusedItem = state.collection.getItem(state.selectionManager.focusedKey);
|
|
13
|
+
const selectedItem = state.selectionManager.selectedKeys.size > 0
|
|
14
|
+
? state.collection.getItem([...state.selectionManager.selectedKeys.values()][0])
|
|
15
|
+
: undefined;
|
|
16
|
+
// Handle scrolling to the item in focus when navigating options via Keyboard - this should only be applied when using a "virtual focus", such as a ComboBox where the browser's focus remains in the <input /> element.
|
|
17
|
+
(0, react_1.useEffect)(() => {
|
|
18
|
+
if (scrollOnFocus && virtuosoRef.current && (focusedItem === null || focusedItem === void 0 ? void 0 : focusedItem.index)) {
|
|
19
|
+
virtuosoRef.current.scrollToIndex({ index: focusedItem.index, align: "center" });
|
|
20
|
+
}
|
|
21
|
+
}, [focusedItem]);
|
|
22
|
+
return ((0, jsx_runtime_1.jsx)(react_virtuoso_1.Virtuoso, { ref: virtuosoRef, totalListHeightChanged: onListHeightChange, totalCount: items.length,
|
|
23
|
+
// Ensure the selected item is visible when the list renders
|
|
24
|
+
initialTopMostItemIndex: selectedItem ? selectedItem.index : 0,
|
|
25
|
+
// We don't really need to set this, but it's handy for tests, which would
|
|
26
|
+
// otherwise render just 1 row. A better way to do this would be to jest.mock
|
|
27
|
+
// out Virtuoso with an impl that just rendered everything, but doing this for now.
|
|
28
|
+
initialItemCount: 5, itemContent: (idx) => {
|
|
29
|
+
var _a;
|
|
30
|
+
const item = items[idx];
|
|
31
|
+
if (item) {
|
|
32
|
+
return ((0, jsx_runtime_1.jsx)(Option_1.Option, { item: item, state: state, contrast: contrast,
|
|
33
|
+
// Only send scrollToIndex functionality forward if we are not auto-scrolling on focus.
|
|
34
|
+
scrollToIndex: scrollOnFocus ? undefined : (_a = virtuosoRef.current) === null || _a === void 0 ? void 0 : _a.scrollToIndex }, item.key));
|
|
35
|
+
}
|
|
36
|
+
} }, void 0));
|
|
37
|
+
}
|
|
38
|
+
exports.VirtualizedOptions = VirtualizedOptions;
|