@homebound/beam 2.91.5 → 2.91.9

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.
@@ -1,5 +1,5 @@
1
1
  import { DOMProps } from "@react-types/shared";
2
- import { AriaAttributes } from "react";
2
+ import React, { AriaAttributes } from "react";
3
3
  import { Margin, Palette, Xss } from "../Css";
4
4
  export interface IconProps extends AriaAttributes, DOMProps {
5
5
  /** The name of an icon */
@@ -11,7 +11,7 @@ export interface IconProps extends AriaAttributes, DOMProps {
11
11
  /** Styles overrides */
12
12
  xss?: Xss<Margin>;
13
13
  }
14
- export declare function Icon(props: IconProps): import("@emotion/react/jsx-runtime").JSX.Element;
14
+ export declare const Icon: React.MemoExoticComponent<(props: IconProps) => import("@emotion/react/jsx-runtime").JSX.Element>;
15
15
  /**
16
16
  * Map of icons paths mapped to their respective name.
17
17
  *
@@ -1,14 +1,17 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.Icons = exports.Icon = void 0;
4
7
  const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
8
+ const react_1 = __importDefault(require("react"));
5
9
  const Css_1 = require("../Css");
6
- function Icon(props) {
10
+ exports.Icon = react_1.default.memo((props) => {
7
11
  const { icon, inc = 3, color = "currentColor", xss, ...other } = props;
8
12
  const size = (0, Css_1.increment)(inc);
9
13
  return ((0, jsx_runtime_1.jsx)("svg", Object.assign({ "aria-hidden": true, width: size, height: size, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", css: { path: Css_1.Css.fill(color).$, ...xss }, "data-icon": icon }, other, { children: exports.Icons[icon] }), void 0));
10
- }
11
- exports.Icon = Icon;
14
+ });
12
15
  /**
13
16
  * Map of icons paths mapped to their respective name.
14
17
  *
@@ -1,4 +1,4 @@
1
- import { LabelHTMLAttributes } from "react";
1
+ import React, { LabelHTMLAttributes } from "react";
2
2
  interface LabelProps {
3
3
  labelProps?: LabelHTMLAttributes<HTMLLabelElement>;
4
4
  label: string;
@@ -7,7 +7,7 @@ interface LabelProps {
7
7
  contrast?: boolean;
8
8
  }
9
9
  /** An internal helper component for rendering form labels. */
10
- export declare function Label(props: LabelProps): import("@emotion/react/jsx-runtime").JSX.Element;
10
+ export declare const Label: React.MemoExoticComponent<(props: LabelProps) => import("@emotion/react/jsx-runtime").JSX.Element>;
11
11
  /** Used for showing labels within text fields. */
12
12
  export declare function InlineLabel({ labelProps, label, contrast, ...others }: LabelProps): import("@emotion/react/jsx-runtime").JSX.Element;
13
13
  export {};
@@ -1,16 +1,19 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.InlineLabel = exports.Label = void 0;
4
7
  const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
8
+ const react_1 = __importDefault(require("react"));
5
9
  const react_aria_1 = require("react-aria");
6
10
  const Css_1 = require("../Css");
7
11
  /** An internal helper component for rendering form labels. */
8
- function Label(props) {
12
+ exports.Label = react_1.default.memo((props) => {
9
13
  const { labelProps, label, hidden, suffix, contrast = false, ...others } = props;
10
14
  const labelEl = ((0, jsx_runtime_1.jsxs)("label", Object.assign({}, labelProps, others, { css: Css_1.Css.dib.sm.gray700.mbPx(4).if(contrast).white.$ }, { children: [label, suffix && ` ${suffix}`] }), void 0));
11
15
  return hidden ? (0, jsx_runtime_1.jsx)(react_aria_1.VisuallyHidden, { children: labelEl }, void 0) : labelEl;
12
- }
13
- exports.Label = Label;
16
+ });
14
17
  /** Used for showing labels within text fields. */
15
18
  function InlineLabel({ labelProps, label, contrast, ...others }) {
16
19
  return ((0, jsx_runtime_1.jsxs)("label", Object.assign({}, labelProps, others, { css: Css_1.Css.smEm.nowrap.gray900.prPx(4).add("color", "currentColor").$ }, { children: [label, ":"] }), void 0));
@@ -260,10 +260,16 @@ function renderTable(style, id, columns, headerRows, filteredRows, firstRowMessa
260
260
  function renderVirtual(style, id, columns, headerRows, filteredRows, firstRowMessage, stickyHeader, firstLastColumnWidth, xss, virtuosoRef) {
261
261
  return ((0, jsx_runtime_1.jsx)(react_virtuoso_1.Virtuoso, { ref: virtuosoRef, components: { List: VirtualRoot(style, columns, id, firstLastColumnWidth, xss) },
262
262
  // Pin/sticky both the header row(s) + firstRowMessage to the top
263
- topItemCount: (stickyHeader ? headerRows.length : 0) + (firstRowMessage ? 1 : 0),
264
- // Both the `Item` and `itemContent` use `display: contents`, so their height is 0,
265
- // so instead drill into the 1st real content cell.
266
- itemSize: (el) => el.firstElementChild.firstElementChild.offsetHeight, itemContent: (index) => {
263
+ topItemCount: (stickyHeader ? headerRows.length : 0) + (firstRowMessage ? 1 : 0), itemSize: (el) => {
264
+ const maybeContentsDiv = el.firstElementChild;
265
+ // If it is a chrome row, then we are not using `display: contents;`, return the height of this element.
266
+ if ("chrome" in maybeContentsDiv.dataset) {
267
+ return maybeContentsDiv.offsetHeight;
268
+ }
269
+ // Both the `Item` and `itemContent` use `display: contents`, so their height is 0,
270
+ // so instead drill into the 1st real content cell.
271
+ return maybeContentsDiv.firstElementChild.offsetHeight;
272
+ }, itemContent: (index) => {
267
273
  // We keep header and filter rows separate, but react-virtuoso is a flat list,
268
274
  // so we pick the right header / first row message / actual row.
269
275
  let i = index;
@@ -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"): JSX.Element;
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[]): import("@emotion/react/jsx-runtime").JSX.Element;
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: JSX.Element[]): void;
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
- let div = (0, jsx_runtime_1.jsx)("div", {}, void 0);
72
- const place = kind === "open" ? "Top" : "Bottom";
73
- const btOrBb = kind === "open" ? "bt" : "bb";
74
- // Create nesting for the all open cards, i.e.:
75
- //
76
- // | card1 | card2 -------------- card2 | card1 |
77
- // | card1 | card2 / ... card3 ... \ card2 | card1 |
78
- // | card1 | card2 | ... card3 ... | card2 | card1 |
79
- //
80
- [...openCards].reverse().forEach((card, i) => {
81
- const first = i === 0;
82
- div = ((0, jsx_runtime_1.jsx)("div", Object.assign({ css: {
83
- ...Css_1.Css.bgColor(card.bgColor).pxPx(card.pxPx).$,
84
- // Only the 1st div needs border left/right radius + border top/bottom.
85
- ...(first &&
86
- Css_1.Css.add({
87
- [`border${place}RightRadius`]: `${card.brPx}px`,
88
- [`border${place}LeftRadius`]: `${card.brPx}px`,
89
- }).hPx(card.brPx).$),
90
- ...(card.bColor && Css_1.Css.bc(card.bColor).bl.br.if(first)[btOrBb].$),
91
- } }, { children: div }), void 0));
92
- });
93
- return div;
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
- let div = (0, jsx_runtime_1.jsx)("div", { css: Css_1.Css.hPx(height).$ }, void 0);
120
- // Start at the current/inside card, and wrap outward padding + borders.
121
- // | card1 | card2 | ... card3 ... | card2 | card1 |
122
- [...openCards].reverse().forEach((card) => {
123
- 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);
124
- });
125
- return div;
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");
@@ -12,19 +13,22 @@ const PresentationContext_1 = require("../components/PresentationContext");
12
13
  const Css_1 = require("../Css");
13
14
  const ChipTextField_1 = require("./ChipTextField");
14
15
  const ListBox_1 = require("./internal/ListBox");
16
+ const ListBoxChip_1 = require("./internal/ListBoxChip");
15
17
  const Value_1 = require("./Value");
16
18
  const utils_1 = require("../utils");
19
+ const options_1 = require("../utils/options");
17
20
  function ChipSelectField(props) {
18
21
  var _a, _b;
22
+ const firstRender = (0, react_1.useRef)(true);
19
23
  const { fieldProps } = (0, PresentationContext_1.usePresentationContext)();
20
- const { label, value, disabled = false, placeholder = "Select an option", options, onSelect, getOptionValue = (opt) => opt.id, // if unset, assume O implements HasId
21
- getOptionLabel = (opt) => opt.name, // if unset, assume O implements HasName
24
+ const { label, value, disabled = false, placeholder = "Select an option", options, onSelect, getOptionValue = options_1.defaultOptionValue, // if unset, assume O implements HasId
25
+ getOptionLabel = options_1.defaultOptionLabel, // if unset, assume O implements HasName
22
26
  onFocus, onBlur, clearable = false, onCreateNew, } = props;
23
27
  const tid = (0, utils_1.useTestIds)(props, "chipSelectField");
24
28
  const typeScale = (_a = fieldProps === null || fieldProps === void 0 ? void 0 : fieldProps.typeScale) !== null && _a !== void 0 ? _a : "sm";
25
29
  const isDisabled = !!disabled;
26
30
  const showClearButton = !disabled && clearable && !!value;
27
- const chipStyles = Css_1.Css[typeScale].tl.bgGray300.gray900.br16.pxPx(10).pyPx(2).$;
31
+ const chipStyles = (0, react_1.useMemo)(() => Css_1.Css[typeScale].tl.bgGray300.gray900.br16.pxPx(10).pyPx(2).$, [typeScale]);
28
32
  // Controls showing the focus border styles.
29
33
  const [visualFocus, setVisualFocus] = (0, react_1.useState)(false);
30
34
  const [isClearFocused, setIsClearFocused] = (0, react_1.useState)(false);
@@ -53,26 +57,43 @@ function ChipSelectField(props) {
53
57
  const wrapperRef = (0, react_1.useRef)(null);
54
58
  // Using `ListData` in order to dynamically update the items
55
59
  const listData = (0, react_stately_1.useListData)({
56
- initialItems: [...options, ...(onCreateNew ? [createNewOpt] : [])],
60
+ initialItems: !onCreateNew
61
+ ? options
62
+ : [
63
+ { title: "Options", options },
64
+ { title: "Actions", isPersistent: true, options: [createNewOpt] },
65
+ ],
57
66
  initialSelectedKeys: [(0, Value_1.valueToKey)(value)],
58
- getKey: (item) => (isPersistentItem(item) ? item.id : getOptionValue(item)),
67
+ getKey: (item) => (isListBoxSection(item) ? item.title : getOptionValue(item)),
59
68
  });
60
- // If the options change, blow away existing items and replace with new values.
61
69
  (0, react_1.useEffect)(() => {
62
- // Create a list of all existing keys to be removed.
63
- const optionKeys = listData.items.reduce(
64
- // Filter out Persistent Items
65
- (acc, o) => (isPersistentItem(o) ? acc : acc.concat(getOptionValue(o))), []);
66
- listData.remove(...optionKeys);
67
- // Using `prepend` to keep Persistent Items (if they exist) at the bottom of the list.
68
- listData.prepend(...options);
70
+ // Avoid unnecessary update of `options` on first render. We define the initial set of items based on the options in the `useListData` hook.
71
+ if (!firstRender.current) {
72
+ if (onCreateNew) {
73
+ // if we have the options in a section, update that section
74
+ listData.update("Options", { title: "Options", options });
75
+ }
76
+ else {
77
+ // otherwise, reset the list completely. We could traverse through the list and update/add/remove when needed, though this is simpler for now.
78
+ listData.remove(...state.collection.getKeys());
79
+ listData.append(...options);
80
+ }
81
+ }
82
+ firstRender.current = false;
69
83
  }, [options]);
70
- const selectChildren = listData.items.map((o) => {
71
- const isPersistent = isPersistentItem(o);
72
- const value = isPersistent ? o.id : getOptionValue(o);
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));
75
- });
84
+ const selectChildren = (0, react_1.useMemo)(() => listData.items.map((s) => {
85
+ if (isListBoxSection(s)) {
86
+ return ((0, jsx_runtime_1.jsx)(react_stately_1.Section, Object.assign({ title: s.title, items: s.options }, { children: (item) => {
87
+ if (isPersistentItem(item)) {
88
+ return ((0, jsx_runtime_1.jsx)(react_stately_1.Item, Object.assign({ textValue: item.name }, { children: item.name }), item.id));
89
+ }
90
+ const label = getOptionLabel(item);
91
+ return ((0, jsx_runtime_1.jsx)(react_stately_1.Item, Object.assign({ textValue: label }, { children: (0, jsx_runtime_1.jsx)(ListBoxChip_1.ListBoxChip, { label: label }, void 0) }), getOptionValue(item)));
92
+ } }), (0, change_case_1.camelCase)(s.title)));
93
+ }
94
+ const label = getOptionLabel(s);
95
+ return ((0, jsx_runtime_1.jsx)(react_stately_1.Item, Object.assign({ textValue: label }, { children: (0, jsx_runtime_1.jsx)(ListBoxChip_1.ListBoxChip, { label: label }, void 0) }), getOptionValue(s)));
96
+ }), [listData.items, getOptionLabel, getOptionValue]);
76
97
  const selectHookProps = {
77
98
  label,
78
99
  isDisabled,
@@ -121,6 +142,7 @@ function ChipSelectField(props) {
121
142
  isOpen: state.isOpen,
122
143
  onClose: state.close,
123
144
  placement: "bottom left",
145
+ offset: 8,
124
146
  });
125
147
  overlayProps.style = {
126
148
  ...overlayProps.style,
@@ -130,17 +152,15 @@ function ChipSelectField(props) {
130
152
  };
131
153
  // State management for the "Create new" flow with ChipTextField.
132
154
  const [showInput, setShowInput] = (0, react_1.useState)(false);
133
- const [inputValue, setInputValue] = (0, react_1.useState)("Add new");
134
155
  const removeCreateNewField = (0, react_1.useCallback)(() => {
135
- var _a;
136
156
  setShowInput(false);
137
- setInputValue("Add new");
138
- (_a = buttonRef.current) === null || _a === void 0 ? void 0 : _a.focus();
139
- }, [setShowInput, setInputValue]);
140
- 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
- await onCreateNew(inputValue);
157
+ // Trigger onBlur to initiate any auto-saving behavior.
158
+ (0, utils_1.maybeCall)(onBlur);
159
+ }, [setShowInput]);
160
+ const field = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [showInput && onCreateNew && ((0, jsx_runtime_1.jsx)(CreateNewField, Object.assign({ onBlur: removeCreateNewField, onEnter: async (value) => {
161
+ await onCreateNew(value);
142
162
  removeCreateNewField();
143
- }, onBlur: removeCreateNewField }, tid.createNewField), void 0)), (0, jsx_runtime_1.jsxs)("div", Object.assign({ ref: wrapperRef, css: {
163
+ } }, tid.createNewField), void 0)), (0, jsx_runtime_1.jsxs)("div", Object.assign({ ref: wrapperRef, css: {
144
164
  ...chipStyles,
145
165
  ...Css_1.Css.dif.relative.p0.mwPx(32).if(!value).bgGray200.$,
146
166
  ...(visualFocus ? Css_1.Css.bshFocus.$ : {}),
@@ -157,7 +177,7 @@ function ChipSelectField(props) {
157
177
  onSelect(undefined, undefined);
158
178
  (0, utils_1.maybeCall)(onBlur);
159
179
  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, positionOffset: 8 }), void 0) }), void 0))] }, void 0));
180
+ }, "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
181
  const tooltipText = selectHookProps.isDisabled && typeof disabled !== "boolean" ? disabled : undefined;
162
182
  return tooltipText ? ((0, jsx_runtime_1.jsx)(components_1.Tooltip, Object.assign({ title: tooltipText, placement: "top" }, { children: field }), void 0)) : (field);
163
183
  }
@@ -175,3 +195,14 @@ function isPersistentKey(key) {
175
195
  return typeof key === "string" && key.startsWith(exports.persistentItemPrefix);
176
196
  }
177
197
  exports.isPersistentKey = isPersistentKey;
198
+ function isListBoxSection(obj) {
199
+ return typeof obj === "object" && "options" in obj;
200
+ }
201
+ exports.isListBoxSection = isListBoxSection;
202
+ // Wrapper for the ChipTextField used in the "Create New" flow on ChipSelectField
203
+ function CreateNewField(props) {
204
+ const { onBlur, onEnter } = props;
205
+ const [value, setValue] = (0, react_1.useState)("Add new");
206
+ const tid = (0, utils_1.useTestIds)(props);
207
+ return ((0, jsx_runtime_1.jsx)(ChipTextField_1.ChipTextField, Object.assign({ autoFocus: true, label: "Add new", value: value, onChange: setValue, onEnter: () => onEnter(value), onBlur: onBlur }, tid), void 0));
208
+ }
@@ -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,51 +4,41 @@ 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
- const ToggleChip_1 = require("../../components/ToggleChip");
9
7
  const Css_1 = require("../../Css");
10
- const Option_1 = require("./Option");
8
+ const constants_1 = require("./constants");
9
+ const ListBoxSection_1 = require("./ListBoxSection");
10
+ const ListBoxToggleChip_1 = require("./ListBoxToggleChip");
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, positionOffset = 4, } = props;
15
- const { listBoxProps } = (0, react_aria_1.useListBox)({ disallowEmptySelection: true, shouldFocusOnHover: true, ...props }, state, listBoxRef);
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 maxListHeight 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`).
18
- // If `maxHeight` is set use that, otherwise use `273` as a default (`42px` is the min-height of each option, so this allows
19
- // 6.5 options in view at a time (doing `.5` so the user can easily tell if there are more).
20
- const maxListHeight = positionMaxHeight && typeof positionMaxHeight === "number" ? positionMaxHeight : 273;
21
- const [listHeight, setListHeight] = (0, react_1.useState)(maxListHeight);
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 virtuosoRef = (0, react_1.useRef)(null);
24
- const focusedItem = state.collection.getItem(state.selectionManager.focusedKey);
25
- // Handle scrolling to the item in focus when navigating options via Keyboard
26
- (0, react_1.useEffect)(() => {
27
- if (virtuosoRef.current && (focusedItem === null || focusedItem === void 0 ? void 0 : focusedItem.index)) {
28
- virtuosoRef.current.scrollToIndex({ index: focusedItem.index, align: "center" });
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.myPx(positionOffset).if(contrast).bgGray700.$,
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(Math.min(maxListHeight, listHeight)).$ }, { children: (0, jsx_runtime_1.jsx)(react_virtuoso_1.Virtuoso, { ref: virtuosoRef, totalListHeightChanged: setListHeight, totalCount: state.collection.size,
35
- // We don't really need to set this, but it's handy for tests, which would
36
- // otherwise render just 1 row. A better way to do this would be to jest.mock
37
- // out Virtuoso with an impl that just rendered everything, but doing this for now.
38
- initialItemCount: 5, itemContent: (idx) => {
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)(ListBoxToggleChip_1.ListBoxToggleChip, { 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
- /** Chip used to display selections within ListBox when using the MultiSelectField */
49
- function ListBoxChip(props) {
50
- const { state, option, getOptionLabel, getOptionValue, disabled = false } = props;
51
- return ((0, jsx_runtime_1.jsx)("li", Object.assign({ css: Css_1.Css.mr1.mb1.$ }, { children: (0, jsx_runtime_1.jsx)(ToggleChip_1.ToggleChip, { text: getOptionLabel(option), onClick: () => {
52
- state.selectionManager.toggleSelection(String(getOptionValue(option)));
53
- }, disabled: disabled }, void 0) }), void 0));
54
- }
43
+ // UX specified maximum height for a ListBox (in pixels)
44
+ const maxPopoverHeight = 512;
@@ -0,0 +1,5 @@
1
+ interface ListBoxChipProps {
2
+ label: string;
3
+ }
4
+ export declare function ListBoxChip({ label }: ListBoxChipProps): import("@emotion/react/jsx-runtime").JSX.Element;
5
+ export {};
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ListBoxChip = void 0;
4
+ const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
5
+ const PresentationContext_1 = require("../../components/PresentationContext");
6
+ const Css_1 = require("../../Css");
7
+ function ListBoxChip({ label }) {
8
+ var _a;
9
+ const { fieldProps } = (0, PresentationContext_1.usePresentationContext)();
10
+ return ((0, jsx_runtime_1.jsx)("span", Object.assign({ css: Css_1.Css[(_a = fieldProps === null || fieldProps === void 0 ? void 0 : fieldProps.typeScale) !== null && _a !== void 0 ? _a : "sm"].tl.bgGray300.gray900.br16.pxPx(10).pyPx(2).lineClamp1.breakAll.$, title: label }, { children: label }), void 0));
11
+ }
12
+ exports.ListBoxChip = ListBoxChip;
@@ -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;
@@ -0,0 +1,12 @@
1
+ import { Key } from "react";
2
+ import { SelectState } from "react-stately";
3
+ interface ListBoxToggleChipProps<O, V extends Key> {
4
+ state: SelectState<O>;
5
+ option: O;
6
+ getOptionLabel: (opt: O) => string;
7
+ getOptionValue: (opt: O) => V;
8
+ disabled?: boolean;
9
+ }
10
+ /** Chip used to display selections within ListBox when using the MultiSelectField */
11
+ export declare function ListBoxToggleChip<O, V extends Key>(props: ListBoxToggleChipProps<O, V>): import("@emotion/react/jsx-runtime").JSX.Element;
12
+ export {};
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ListBoxToggleChip = void 0;
4
+ const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
5
+ const components_1 = require("../../components");
6
+ const Css_1 = require("../../Css");
7
+ /** Chip used to display selections within ListBox when using the MultiSelectField */
8
+ function ListBoxToggleChip(props) {
9
+ const { state, option, getOptionLabel, getOptionValue, disabled = false } = props;
10
+ return ((0, jsx_runtime_1.jsx)("li", Object.assign({ css: Css_1.Css.mr1.mb1.$ }, { children: (0, jsx_runtime_1.jsx)(components_1.ToggleChip, { text: getOptionLabel(option), onClick: () => {
11
+ state.selectionManager.toggleSelection(String(getOptionValue(option)));
12
+ }, disabled: disabled }, void 0) }), void 0));
13
+ }
14
+ exports.ListBoxToggleChip = ListBoxToggleChip;
@@ -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
- return ((0, jsx_runtime_1.jsxs)("li", Object.assign({}, (0, react_aria_1.mergeProps)(optionProps, hoverProps), { ref: ref, css: {
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;
@@ -0,0 +1,2 @@
1
+ export declare const persistentItemHeight = 42;
2
+ export declare const sectionSeparatorHeight = 1;
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sectionSeparatorHeight = exports.persistentItemHeight = void 0;
4
+ exports.persistentItemHeight = 42;
5
+ exports.sectionSeparatorHeight = 1;
@@ -0,0 +1,2 @@
1
+ export declare const defaultOptionValue: <O>(opt: O) => any;
2
+ export declare const defaultOptionLabel: <O>(opt: O) => any;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defaultOptionLabel = exports.defaultOptionValue = void 0;
4
+ const defaultOptionValue = (opt) => opt.id;
5
+ exports.defaultOptionValue = defaultOptionValue;
6
+ const defaultOptionLabel = (opt) => opt.name;
7
+ exports.defaultOptionLabel = defaultOptionLabel;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homebound/beam",
3
- "version": "2.91.5",
3
+ "version": "2.91.9",
4
4
  "author": "Homebound",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -119,6 +119,7 @@
119
119
  "react-beautiful-dnd": "^13.1.0",
120
120
  "react-dom": "^16.14.0",
121
121
  "semantic-release": "^17.4.0",
122
+ "storybook-addon-performance": "^0.16.1",
122
123
  "ts-jest": "^26.5.3",
123
124
  "ts-node": "^9.1.1",
124
125
  "tslib": "^2.1.0",