@homebound/beam 2.91.3 → 2.91.7
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/Modal/useModal.js +5 -0
- package/dist/components/Table/nestedCards.d.ts +11 -3
- package/dist/components/Table/nestedCards.js +44 -48
- 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
|
@@ -7,6 +7,11 @@ const utils_1 = require("../../utils");
|
|
|
7
7
|
function useModal() {
|
|
8
8
|
const { modalState, modalCanCloseChecks } = (0, BeamContext_1.useBeamContext)();
|
|
9
9
|
const lastCanClose = (0, react_1.useRef)();
|
|
10
|
+
(0, react_1.useEffect)(() => {
|
|
11
|
+
return () => {
|
|
12
|
+
modalCanCloseChecks.current = modalCanCloseChecks.current.filter((c) => c !== lastCanClose.current);
|
|
13
|
+
};
|
|
14
|
+
}, [modalCanCloseChecks]);
|
|
10
15
|
return (0, react_1.useMemo)(() => ({
|
|
11
16
|
openModal(props) {
|
|
12
17
|
// TODO Check already open?
|
|
@@ -12,6 +12,8 @@ import { GridColumn, GridDataRow, GridStyle, Kinded, NestedCardStyle, RowTuple }
|
|
|
12
12
|
* a content row itself, the nested padding is handled separately by the
|
|
13
13
|
* GridRow component.
|
|
14
14
|
*/
|
|
15
|
+
declare type Chrome = () => JSX.Element;
|
|
16
|
+
declare type ChromeBuffer = Chrome[];
|
|
15
17
|
export declare class NestedCards {
|
|
16
18
|
private columns;
|
|
17
19
|
private filteredRows;
|
|
@@ -40,7 +42,7 @@ export declare class NestedCards {
|
|
|
40
42
|
* I.e. due to the flatness of our DOM, we inherently have to add a "close"
|
|
41
43
|
* row separate from the card's actual content.
|
|
42
44
|
*/
|
|
43
|
-
export declare function makeOpenOrCloseCard(openCards: NestedCardStyle[], kind: "open" | "close"):
|
|
45
|
+
export declare function makeOpenOrCloseCard(openCards: NestedCardStyle[], kind: "open" | "close"): Chrome;
|
|
44
46
|
/**
|
|
45
47
|
* For the first or last cell of actual content, wrap them in divs that re-create the
|
|
46
48
|
* outer cards' padding + background.
|
|
@@ -52,7 +54,7 @@ export declare function maybeAddCardPadding(openCards: NestedCardStyle[], column
|
|
|
52
54
|
* Our height is not based on `openCards`, b/c for the top-most level, we won't
|
|
53
55
|
* have any open cards, but still want a space between top-level cards.
|
|
54
56
|
*/
|
|
55
|
-
export declare function makeSpacer(height: number, openCards: NestedCardStyle[]):
|
|
57
|
+
export declare function makeSpacer(height: number, openCards: NestedCardStyle[]): Chrome;
|
|
56
58
|
/**
|
|
57
59
|
* Takes the current buffer of close row(s), spacers, and open row, and creates a single chrome DOM row.
|
|
58
60
|
*
|
|
@@ -65,5 +67,11 @@ export declare function makeSpacer(height: number, openCards: NestedCardStyle[])
|
|
|
65
67
|
* - nested card2 content row
|
|
66
68
|
* - chrome row (card2 close, card1 close)
|
|
67
69
|
*/
|
|
68
|
-
export declare function maybeCreateChromeRow(columns: GridColumn<any>[], filteredRows: RowTuple<any>[], chromeBuffer:
|
|
70
|
+
export declare function maybeCreateChromeRow(columns: GridColumn<any>[], filteredRows: RowTuple<any>[], chromeBuffer: ChromeBuffer): void;
|
|
71
|
+
interface ChromeRowProps {
|
|
72
|
+
chromeBuffer: ChromeBuffer;
|
|
73
|
+
columns: number;
|
|
74
|
+
}
|
|
75
|
+
export declare function ChromeRow({ chromeBuffer, columns }: ChromeRowProps): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
69
76
|
export declare function dropChromeRows<R extends Kinded>(filteredRows: RowTuple<R>[]): [GridDataRow<R>, ReactElement][];
|
|
77
|
+
export {};
|
|
@@ -1,21 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.dropChromeRows = exports.maybeCreateChromeRow = exports.makeSpacer = exports.maybeAddCardPadding = exports.makeOpenOrCloseCard = exports.NestedCards = void 0;
|
|
3
|
+
exports.dropChromeRows = exports.ChromeRow = exports.maybeCreateChromeRow = exports.makeSpacer = exports.maybeAddCardPadding = exports.makeOpenOrCloseCard = exports.NestedCards = void 0;
|
|
4
4
|
const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
|
|
5
5
|
const react_1 = require("react");
|
|
6
6
|
const Css_1 = require("../../Css");
|
|
7
|
-
/**
|
|
8
|
-
* A helper class to create our nested card DOM shenanigans.
|
|
9
|
-
*
|
|
10
|
-
* This acts as a one-off visitor that accepts "begin row", "between row",
|
|
11
|
-
* "end row" calls from GridTable while its translating the user's nested
|
|
12
|
-
* GridDataRows into a flat list of RowTuples, and interjects fake/chrome
|
|
13
|
-
* rows into `filteredRows` as necessary.
|
|
14
|
-
*
|
|
15
|
-
* Note that this class only handles *between row* chrome and that within
|
|
16
|
-
* a content row itself, the nested padding is handled separately by the
|
|
17
|
-
* GridRow component.
|
|
18
|
-
*/
|
|
19
7
|
class NestedCards {
|
|
20
8
|
constructor(columns, filteredRows, style) {
|
|
21
9
|
this.columns = columns;
|
|
@@ -68,29 +56,32 @@ exports.NestedCards = NestedCards;
|
|
|
68
56
|
* row separate from the card's actual content.
|
|
69
57
|
*/
|
|
70
58
|
function makeOpenOrCloseCard(openCards, kind) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
59
|
+
const scopeCards = [...openCards];
|
|
60
|
+
return () => {
|
|
61
|
+
let div = (0, jsx_runtime_1.jsx)("div", {}, void 0);
|
|
62
|
+
const place = kind === "open" ? "Top" : "Bottom";
|
|
63
|
+
const btOrBb = kind === "open" ? "bt" : "bb";
|
|
64
|
+
// Create nesting for the all open cards, i.e.:
|
|
65
|
+
//
|
|
66
|
+
// | card1 | card2 -------------- card2 | card1 |
|
|
67
|
+
// | card1 | card2 / ... card3 ... \ card2 | card1 |
|
|
68
|
+
// | card1 | card2 | ... card3 ... | card2 | card1 |
|
|
69
|
+
//
|
|
70
|
+
[...scopeCards].reverse().forEach((card, i) => {
|
|
71
|
+
const first = i === 0;
|
|
72
|
+
div = ((0, jsx_runtime_1.jsx)("div", Object.assign({ css: {
|
|
73
|
+
...Css_1.Css.bgColor(card.bgColor).pxPx(card.pxPx).$,
|
|
74
|
+
// Only the 1st div needs border left/right radius + border top/bottom.
|
|
75
|
+
...(first &&
|
|
76
|
+
Css_1.Css.add({
|
|
77
|
+
[`border${place}RightRadius`]: `${card.brPx}px`,
|
|
78
|
+
[`border${place}LeftRadius`]: `${card.brPx}px`,
|
|
79
|
+
}).hPx(card.brPx).$),
|
|
80
|
+
...(card.bColor && Css_1.Css.bc(card.bColor).bl.br.if(first)[btOrBb].$),
|
|
81
|
+
} }, { children: div }), void 0));
|
|
82
|
+
});
|
|
83
|
+
return div;
|
|
84
|
+
};
|
|
94
85
|
}
|
|
95
86
|
exports.makeOpenOrCloseCard = makeOpenOrCloseCard;
|
|
96
87
|
/**
|
|
@@ -116,13 +107,16 @@ exports.maybeAddCardPadding = maybeAddCardPadding;
|
|
|
116
107
|
* have any open cards, but still want a space between top-level cards.
|
|
117
108
|
*/
|
|
118
109
|
function makeSpacer(height, openCards) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
110
|
+
const scopeCards = [...openCards];
|
|
111
|
+
return () => {
|
|
112
|
+
let div = (0, jsx_runtime_1.jsx)("div", { css: Css_1.Css.hPx(height).$ }, void 0);
|
|
113
|
+
// Start at the current/inside card, and wrap outward padding + borders.
|
|
114
|
+
// | card1 | card2 | ... card3 ... | card2 | card1 |
|
|
115
|
+
[...scopeCards].reverse().forEach((card) => {
|
|
116
|
+
div = (0, jsx_runtime_1.jsx)("div", Object.assign({ css: Css_1.Css.bgColor(card.bgColor).pxPx(card.pxPx).if(!!card.bColor).bc(card.bColor).bl.br.$ }, { children: div }), void 0);
|
|
117
|
+
});
|
|
118
|
+
return div;
|
|
119
|
+
};
|
|
126
120
|
}
|
|
127
121
|
exports.makeSpacer = makeSpacer;
|
|
128
122
|
/**
|
|
@@ -139,16 +133,18 @@ exports.makeSpacer = makeSpacer;
|
|
|
139
133
|
*/
|
|
140
134
|
function maybeCreateChromeRow(columns, filteredRows, chromeBuffer) {
|
|
141
135
|
if (chromeBuffer.length > 0) {
|
|
142
|
-
filteredRows.push([
|
|
143
|
-
undefined,
|
|
144
|
-
// We add 2 to account for our dedicated open/close columns
|
|
145
|
-
(0, jsx_runtime_1.jsx)("div", Object.assign({ css: Css_1.Css.gc(`span ${columns.length + 2}`).$, "data-chrome": "true" }, { children: chromeBuffer.map((c, i) => ((0, jsx_runtime_1.jsx)(react_1.Fragment, { children: c }, i))) }), void 0),
|
|
146
|
-
]);
|
|
136
|
+
filteredRows.push([undefined, (0, jsx_runtime_1.jsx)(ChromeRow, { chromeBuffer: [...chromeBuffer], columns: columns.length }, void 0)]);
|
|
147
137
|
// clear the buffer
|
|
148
138
|
chromeBuffer.splice(0, chromeBuffer.length);
|
|
149
139
|
}
|
|
150
140
|
}
|
|
151
141
|
exports.maybeCreateChromeRow = maybeCreateChromeRow;
|
|
142
|
+
function ChromeRow({ chromeBuffer, columns }) {
|
|
143
|
+
return (
|
|
144
|
+
// We add 2 to account for our dedicated open/close columns
|
|
145
|
+
(0, jsx_runtime_1.jsx)("div", Object.assign({ css: Css_1.Css.gc(`span ${columns + 2}`).$, "data-chrome": "true" }, { children: chromeBuffer.map((c, i) => ((0, jsx_runtime_1.jsx)(react_1.Fragment, { children: c() }, i))) }), void 0));
|
|
146
|
+
}
|
|
147
|
+
exports.ChromeRow = ChromeRow;
|
|
152
148
|
function dropChromeRows(filteredRows) {
|
|
153
149
|
return filteredRows.filter(([r]) => !!r);
|
|
154
150
|
}
|
|
@@ -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;
|