@homebound/beam 2.135.2 → 2.137.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,6 @@
1
+ interface AutoSaveIndicatorProps {
2
+ hideOnIdle?: boolean;
3
+ doNotReset?: boolean;
4
+ }
5
+ export declare function AutoSaveIndicator({ hideOnIdle, doNotReset }: AutoSaveIndicatorProps): import("@emotion/react/jsx-runtime").JSX.Element | null;
6
+ export {};
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AutoSaveIndicator = void 0;
4
+ const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
5
+ const form_state_1 = require("@homebound/form-state");
6
+ const react_1 = require("react");
7
+ const Css_1 = require("../Css");
8
+ const types_1 = require("../types");
9
+ const _1 = require(".");
10
+ const Icon_1 = require("./Icon");
11
+ function AutoSaveIndicator({ hideOnIdle, doNotReset }) {
12
+ const { status, resetStatus, errors } = (0, form_state_1.useAutoSaveStatus)();
13
+ (0, react_1.useEffect)(() => {
14
+ if (doNotReset)
15
+ return;
16
+ /**
17
+ * Any time AutoSaveIndicator dismounts, most likely on Page Navigation,
18
+ * state should clear before the next Indicator mounts
19
+ */
20
+ return () => resetStatus();
21
+ }, []);
22
+ switch (status) {
23
+ case form_state_1.AutoSaveStatus.IDLE:
24
+ return hideOnIdle ? null : (0, jsx_runtime_1.jsx)(Icon_1.Icon, { icon: "cloudSave", color: Css_1.Palette.Gray700 }, void 0);
25
+ case form_state_1.AutoSaveStatus.SAVING:
26
+ return ((0, jsx_runtime_1.jsx)(HelperText, Object.assign({ text: "Saving...", color: Css_1.Palette.Gray700 }, { children: (0, jsx_runtime_1.jsx)(Icon_1.Icon, { icon: "refresh", color: Css_1.Palette.Gray700 }, void 0) }), void 0));
27
+ case form_state_1.AutoSaveStatus.DONE:
28
+ return ((0, jsx_runtime_1.jsx)(HelperText, Object.assign({ text: "Saved", color: Css_1.Palette.LightBlue700 }, { children: (0, jsx_runtime_1.jsx)(Icon_1.Icon, { icon: "cloudSave", color: Css_1.Palette.LightBlue700 }, void 0) }), void 0));
29
+ case form_state_1.AutoSaveStatus.ERROR:
30
+ return (
31
+ /**
32
+ * Tooltip is expanding to fill as much available space as it can, possibly
33
+ * rendering it far away from the Icon/Text. Wrap it with a div to constrain
34
+ * it.
35
+ */
36
+ (0, jsx_runtime_1.jsx)("div", Object.assign({ css: Css_1.Css.dif.$ }, { children: (0, jsx_runtime_1.jsx)(_1.Tooltip, Object.assign({ title: String(errors), placement: "bottom" }, { children: (0, jsx_runtime_1.jsx)(HelperText, Object.assign({ text: "Error saving", color: Css_1.Palette.Gray700 }, { children: (0, jsx_runtime_1.jsx)(Icon_1.Icon, { icon: "error", color: Css_1.Palette.Red500 }, void 0) }), void 0) }), void 0) }), void 0));
37
+ default:
38
+ (0, types_1.assertNever)(status);
39
+ }
40
+ }
41
+ exports.AutoSaveIndicator = AutoSaveIndicator;
42
+ const HelperText = ({ text, color, children }) => ((0, jsx_runtime_1.jsxs)("div", Object.assign({ css: Css_1.Css.df.gap1.color(color).$ }, { children: [children, text] }), void 0));
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useBeamContext = exports.BeamProvider = exports.BeamContext = void 0;
4
4
  const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
5
+ const form_state_1 = require("@homebound/form-state");
5
6
  const react_1 = require("react");
6
7
  const react_aria_1 = require("react-aria");
7
8
  const Modal_1 = require("./Modal/Modal");
@@ -56,7 +57,7 @@ function BeamProvider({ children, ...presentationProps }) {
56
57
  tabActionsDiv,
57
58
  };
58
59
  }, [modalBodyDiv, modalFooterDiv]);
59
- return ((0, jsx_runtime_1.jsx)(exports.BeamContext.Provider, Object.assign({ value: { ...context } }, { children: (0, jsx_runtime_1.jsx)(PresentationContext_1.PresentationProvider, Object.assign({}, presentationProps, { children: (0, jsx_runtime_1.jsxs)(SnackbarContext_1.SnackbarProvider, { children: [(0, jsx_runtime_1.jsxs)(react_aria_1.OverlayProvider, { children: [children, modalRef.current && drawerContentStackRef.current.length === 0 && (0, jsx_runtime_1.jsx)(Modal_1.Modal, Object.assign({}, modalRef.current), void 0)] }, void 0), (0, jsx_runtime_1.jsx)(SuperDrawer_1.SuperDrawer, {}, void 0)] }, void 0) }), void 0) }), void 0));
60
+ return ((0, jsx_runtime_1.jsx)(exports.BeamContext.Provider, Object.assign({ value: { ...context } }, { children: (0, jsx_runtime_1.jsx)(PresentationContext_1.PresentationProvider, Object.assign({}, presentationProps, { children: (0, jsx_runtime_1.jsx)(form_state_1.AutoSaveStatusProvider, { children: (0, jsx_runtime_1.jsxs)(SnackbarContext_1.SnackbarProvider, { children: [(0, jsx_runtime_1.jsxs)(react_aria_1.OverlayProvider, { children: [children, modalRef.current && drawerContentStackRef.current.length === 0 && (0, jsx_runtime_1.jsx)(Modal_1.Modal, Object.assign({}, modalRef.current), void 0)] }, void 0), (0, jsx_runtime_1.jsx)(SuperDrawer_1.SuperDrawer, {}, void 0)] }, void 0) }, void 0) }), void 0) }), void 0));
60
61
  }
61
62
  exports.BeamProvider = BeamProvider;
62
63
  /** Looks like a ref, but invokes a re-render on set (w/o changing the setter identity). */
@@ -29,7 +29,7 @@ function Filters(props) {
29
29
  return [Object.fromEntries(impls), {}];
30
30
  }, [numberOfPageFilters, filterDefs]);
31
31
  const numModalFilters = (0, utils_1.safeKeys)(modalFilters).filter((fk) => filter[fk] !== undefined).length;
32
- const maybeGroupByField = groupBy ? ((0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)(SelectField_1.SelectField, { label: "Group by", compact: !vertical, inlineLabel: !vertical, sizeToContent: !vertical, options: groupBy.options, getOptionValue: (o) => o.id, getOptionLabel: (o) => o.name, value: groupBy.value, onSelect: (g) => groupBy.setValue(g) }, void 0) }, void 0)) : null;
32
+ const maybeGroupByField = groupBy ? ((0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)(SelectField_1.SelectField, { label: "Group by", compact: !vertical, inlineLabel: !vertical, sizeToContent: !vertical, options: groupBy.options, getOptionValue: (o) => o.id, getOptionLabel: (o) => o.name, value: groupBy.value, onSelect: (g) => g && groupBy.setValue(g) }, void 0) }, void 0)) : null;
33
33
  // Return list of filter components. `onSelect` should update the `filter`
34
34
  return ((0, jsx_runtime_1.jsxs)("div", Object.assign({ css: {
35
35
  ...(vertical ? Css_1.Css.df.fdc.childGap2.$ : Css_1.Css.df.aic.childGap1.$),
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.ModalFooter = exports.ModalBody = exports.ModalHeader = exports.Modal = void 0;
7
7
  const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
8
+ const form_state_1 = require("@homebound/form-state");
8
9
  const resize_observer_1 = __importDefault(require("@react-hook/resize-observer"));
9
10
  const react_1 = require("react");
10
11
  const react_aria_1 = require("react-aria");
@@ -64,12 +65,12 @@ function Modal(props) {
64
65
  modalBodyRef.current.appendChild(modalBodyDiv);
65
66
  modalFooterRef.current.appendChild(modalFooterDiv);
66
67
  }, [modalBodyRef, modalFooterRef, modalHeaderRef]);
67
- return ((0, jsx_runtime_1.jsx)(react_aria_1.OverlayContainer, { children: (0, jsx_runtime_1.jsx)("div", Object.assign({ css: Css_1.Css.underlay.z4.$ }, underlayProps, testId.underlay, { children: (0, jsx_runtime_1.jsx)(react_aria_1.FocusScope, Object.assign({ contain: true, restoreFocus: true, autoFocus: true }, { children: (0, jsx_runtime_1.jsxs)("div", Object.assign({ css: Css_1.Css.br24.bgWhite.bshModal.overflowHidden
68
- .maxh("90vh")
69
- .df.fdc.wPx(width)
70
- .mh((0, Css_1.px)(defaultMinHeight))
71
- .if(isFixedHeight)
72
- .hPx(height).$, ref: ref }, overlayProps, dialogProps, modalProps, testId, { children: [(0, jsx_runtime_1.jsxs)("header", Object.assign({ css: Css_1.Css.df.p3.fs0.if(drawHeaderBorder).bb.bGray200.$ }, { children: [(0, jsx_runtime_1.jsx)("h1", Object.assign({ css: Css_1.Css.fg1.xl2Em.gray900.$, ref: modalHeaderRef }, titleProps, testId.title), void 0), (0, jsx_runtime_1.jsx)("span", Object.assign({ css: Css_1.Css.fs0.pl1.$ }, { children: (0, jsx_runtime_1.jsx)(IconButton_1.IconButton, Object.assign({ icon: "x", onClick: closeModal }, testId.titleClose), void 0) }), void 0)] }), void 0), (0, jsx_runtime_1.jsxs)("main", Object.assign({ ref: contentRef, css: Css_1.Css.fg1.overflowYAuto.if(hasScroll).bb.bGray200.if(!!forceScrolling).overflowYScroll.$ }, { children: [content, (0, jsx_runtime_1.jsx)("div", { ref: modalBodyRef }, void 0)] }), void 0), (0, jsx_runtime_1.jsx)("footer", Object.assign({ css: Css_1.Css.fs0.$ }, { children: (0, jsx_runtime_1.jsx)("div", { ref: modalFooterRef }, void 0) }), void 0)] }), void 0) }), void 0) }), void 0) }, void 0));
68
+ return ((0, jsx_runtime_1.jsx)(react_aria_1.OverlayContainer, { children: (0, jsx_runtime_1.jsx)(form_state_1.AutoSaveStatusProvider, { children: (0, jsx_runtime_1.jsx)("div", Object.assign({ css: Css_1.Css.underlay.z4.$ }, underlayProps, testId.underlay, { children: (0, jsx_runtime_1.jsx)(react_aria_1.FocusScope, Object.assign({ contain: true, restoreFocus: true, autoFocus: true }, { children: (0, jsx_runtime_1.jsxs)("div", Object.assign({ css: Css_1.Css.br24.bgWhite.bshModal.overflowHidden
69
+ .maxh("90vh")
70
+ .df.fdc.wPx(width)
71
+ .mh((0, Css_1.px)(defaultMinHeight))
72
+ .if(isFixedHeight)
73
+ .hPx(height).$, ref: ref }, overlayProps, dialogProps, modalProps, testId, { children: [(0, jsx_runtime_1.jsxs)("header", Object.assign({ css: Css_1.Css.df.p3.fs0.if(drawHeaderBorder).bb.bGray200.$ }, { children: [(0, jsx_runtime_1.jsx)("h1", Object.assign({ css: Css_1.Css.fg1.xl2Em.gray900.$, ref: modalHeaderRef }, titleProps, testId.title), void 0), (0, jsx_runtime_1.jsx)("span", Object.assign({ css: Css_1.Css.fs0.pl1.$ }, { children: (0, jsx_runtime_1.jsx)(IconButton_1.IconButton, Object.assign({ icon: "x", onClick: closeModal }, testId.titleClose), void 0) }), void 0)] }), void 0), (0, jsx_runtime_1.jsxs)("main", Object.assign({ ref: contentRef, css: Css_1.Css.fg1.overflowYAuto.if(hasScroll).bb.bGray200.if(!!forceScrolling).overflowYScroll.$ }, { children: [content, (0, jsx_runtime_1.jsx)("div", { ref: modalBodyRef }, void 0)] }), void 0), (0, jsx_runtime_1.jsx)("footer", Object.assign({ css: Css_1.Css.fs0.$ }, { children: (0, jsx_runtime_1.jsx)("div", { ref: modalFooterRef }, void 0) }), void 0)] }), void 0) }), void 0) }), void 0) }, void 0) }, void 0));
73
74
  }
74
75
  exports.Modal = Modal;
75
76
  function ModalHeader({ children }) {
@@ -4,6 +4,7 @@ exports.SuperDrawer = void 0;
4
4
  const react_1 = require("@emotion/react");
5
5
  const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
6
6
  const react_2 = require("@emotion/react");
7
+ const form_state_1 = require("@homebound/form-state");
7
8
  const framer_motion_1 = require("framer-motion");
8
9
  const react_3 = require("react");
9
10
  const react_dom_1 = require("react-dom");
@@ -71,29 +72,29 @@ function SuperDrawer() {
71
72
  animate: { opacity: 1 },
72
73
  // Unmount styles
73
74
  exit: { opacity: 0, transition: { delay: 0.2 } }, onClick: closeDrawer },
74
- (0, jsx_runtime_1.jsxs)(framer_motion_1.motion.aside, Object.assign({ css: Css_1.Css.bgWhite.h100.maxw((0, Css_1.px)(1040)).w100.df.fdc.relative.$,
75
+ (0, jsx_runtime_1.jsx)(framer_motion_1.motion.aside, Object.assign({ css: Css_1.Css.bgWhite.h100.maxw((0, Css_1.px)(1040)).w100.df.fdc.relative.$,
75
76
  // Keeping initial x to 1040 as this will still work if the container is smaller
76
77
  initial: { x: 1040 }, animate: { x: 0 },
77
78
  // Custom transitions settings for the translateX animation
78
79
  transition: { ease: "linear", duration: 0.2, delay: 0.2 }, exit: { transition: { ease: "linear", duration: 0.2 }, x: 1040 },
79
80
  // Preventing clicks from triggering parent onClick
80
- onClick: (e) => e.stopPropagation() }, { children: [(0, jsx_runtime_1.jsxs)("header", Object.assign({ css: Css_1.Css.df.p3.bb.bGray200.df.aic.jcsb.gap2.$ }, { children: [(0, jsx_runtime_1.jsxs)("div", Object.assign({ css: Css_1.Css.df.aic.$ }, { children: [(0, jsx_runtime_1.jsx)("div", Object.assign({ css: Css_1.Css.xl2Em.gray900.mr2.$ }, testId.title, { ref: drawerHeaderRef }, { children: !modalState.current && (title || null) }), void 0), !modalState.current && (titleLeftContent || null)] }), void 0), !modalState.current && (
81
- // Forcing height to 32px to match title height
82
- (0, jsx_runtime_1.jsxs)("div", Object.assign({ css: Css_1.Css.df.childGap3.aic.hPx(32).fs0.$ }, { children: [titleRightContent || null, (0, jsx_runtime_1.jsx)(components_1.ButtonGroup, Object.assign({ buttons: [
83
- {
84
- icon: "chevronLeft",
85
- onClick: () => onPrevClick && onPrevClick(),
86
- disabled: !onPrevClick || isDetail,
87
- },
88
- {
89
- icon: "chevronRight",
90
- onClick: () => onNextClick && onNextClick(),
91
- disabled: !onNextClick || isDetail,
92
- },
93
- ] }, testId.headerActions), void 0), (0, jsx_runtime_1.jsx)(components_1.IconButton, Object.assign({ icon: "x", onClick: closeDrawer }, testId.close), void 0)] }), void 0))] }), void 0), content, modalState.current && (
94
- // Forcing some design constraints on the modal component
95
- (0, jsx_runtime_1.jsxs)("div", Object.assign({ css:
96
- // topPX(81) is the offset from the header
97
- Css_1.Css.fg1.topPx(81).left0.right0.bottom0.absolute.bgWhite.df.aic.jcc.fg1.fdc.z5.$ }, { children: [modalState.current.content, (0, jsx_runtime_1.jsx)("div", { ref: modalBodyRef }, void 0), (0, jsx_runtime_1.jsx)("div", { ref: modalFooterRef }, void 0)] }), void 0))] }), "superDrawerContainer"))] }, void 0)) }, void 0), document.body);
81
+ onClick: (e) => e.stopPropagation() }, { children: (0, jsx_runtime_1.jsxs)(form_state_1.AutoSaveStatusProvider, { children: [(0, jsx_runtime_1.jsxs)("header", Object.assign({ css: Css_1.Css.df.p3.bb.bGray200.df.aic.jcsb.gap2.$ }, { children: [(0, jsx_runtime_1.jsxs)("div", Object.assign({ css: Css_1.Css.df.aic.$ }, { children: [(0, jsx_runtime_1.jsx)("div", Object.assign({ css: Css_1.Css.xl2Em.gray900.mr2.$ }, testId.title, { ref: drawerHeaderRef }, { children: !modalState.current && (title || null) }), void 0), !modalState.current && (titleLeftContent || null)] }), void 0), !modalState.current && (
82
+ // Forcing height to 32px to match title height
83
+ (0, jsx_runtime_1.jsxs)("div", Object.assign({ css: Css_1.Css.df.childGap3.aic.hPx(32).fs0.$ }, { children: [titleRightContent || null, (0, jsx_runtime_1.jsx)(components_1.ButtonGroup, Object.assign({ buttons: [
84
+ {
85
+ icon: "chevronLeft",
86
+ onClick: () => onPrevClick && onPrevClick(),
87
+ disabled: !onPrevClick || isDetail,
88
+ },
89
+ {
90
+ icon: "chevronRight",
91
+ onClick: () => onNextClick && onNextClick(),
92
+ disabled: !onNextClick || isDetail,
93
+ },
94
+ ] }, testId.headerActions), void 0), (0, jsx_runtime_1.jsx)(components_1.IconButton, Object.assign({ icon: "x", onClick: closeDrawer }, testId.close), void 0)] }), void 0))] }), void 0), content, modalState.current && (
95
+ // Forcing some design constraints on the modal component
96
+ (0, jsx_runtime_1.jsxs)("div", Object.assign({ css:
97
+ // topPX(81) is the offset from the header
98
+ Css_1.Css.fg1.topPx(81).left0.right0.bottom0.absolute.bgWhite.df.aic.jcc.fg1.fdc.z5.$ }, { children: [modalState.current.content, (0, jsx_runtime_1.jsx)("div", { ref: modalBodyRef }, void 0), (0, jsx_runtime_1.jsx)("div", { ref: modalFooterRef }, void 0)] }), void 0))] }, void 0) }), "superDrawerContainer"))] }, void 0)) }, void 0), document.body);
98
99
  }
99
100
  exports.SuperDrawer = SuperDrawer;
@@ -140,8 +140,8 @@ function GridTable(props) {
140
140
  }
141
141
  else {
142
142
  // Otherwise, maybe one of the children match.
143
- const isCollapsed = collapsedIds.includes(row.id);
144
- if (!isCollapsed && !!((_c = row.children) === null || _c === void 0 ? void 0 : _c.length)) {
143
+ // Always filter children rows whether or not they are collapsed in order to determine proper "selected" state of the group row.
144
+ if (!!((_c = row.children) === null || _c === void 0 ? void 0 : _c.length)) {
145
145
  const matchedChildren = row.children.reduce(filterRows, []);
146
146
  // If some children did match, then add the parent row with its matched children.
147
147
  if (matchedChildren.length > 0) {
@@ -152,7 +152,7 @@ function GridTable(props) {
152
152
  return acc;
153
153
  }, [filter, collapsedIds, columns]);
154
154
  // Flatten + component-ize the sorted rows.
155
- let [headerRows, filteredRows, totalsRows] = (0, react_1.useMemo)(() => {
155
+ let [headerRows, visibleDataRows, totalsRows, filteredRowIds] = (0, react_1.useMemo)(() => {
156
156
  function makeRowComponent(row, level) {
157
157
  // We only pass sortState to header rows, b/c non-headers rows shouldn't have to re-render on sorting
158
158
  // changes, and so by not passing the sortProps, it means the data rows' React.memo will still cache them.
@@ -178,20 +178,25 @@ function GridTable(props) {
178
178
  // Split out the header rows from the data rows so that we can put an `infoMessage` in between them (if needed).
179
179
  const headerRows = [];
180
180
  const totalsRows = [];
181
- const filteredRows = [];
181
+ const visibleDataRows = [];
182
+ const filteredRowIds = [];
182
183
  // Misc state to track our nested card-ification, i.e. interleaved actual rows + chrome rows
183
- const nestedCards = !!style.nestedCards && new nestedCards_1.NestedCards(columns, filteredRows, style.nestedCards);
184
- function visit([row, children], level) {
185
- let isCard = nestedCards && nestedCards.maybeOpenCard(row);
186
- filteredRows.push([row, makeRowComponent(row, level)]);
187
- const isCollapsed = collapsedIds.includes(row.id);
188
- if (!isCollapsed && children.length) {
189
- nestedCards && nestedCards.addSpacer();
190
- visitRows(children, isCard, level + 1);
184
+ const nestedCards = !!style.nestedCards && new nestedCards_1.NestedCards(columns, visibleDataRows, style.nestedCards);
185
+ function visit([row, children], level, visible) {
186
+ let isCard = visible && nestedCards && nestedCards.maybeOpenCard(row);
187
+ visible && visibleDataRows.push([row, makeRowComponent(row, level)]);
188
+ // This row may be invisible (because it's parent is collapsed), but we still want
189
+ // to consider it matched if it or it's parent matched a filter.
190
+ filteredRowIds.push(row.id);
191
+ if (children.length) {
192
+ // Consider "isCollapsed" as true if the parent wasn't visible.
193
+ const isCollapsed = !visible || collapsedIds.includes(row.id);
194
+ !isCollapsed && nestedCards && nestedCards.addSpacer();
195
+ visitRows(children, isCard, level + 1, !isCollapsed);
191
196
  }
192
- !(0, nestedCards_1.isLeafRow)(row) && isCard && nestedCards && nestedCards.closeCard();
197
+ visible && !(0, nestedCards_1.isLeafRow)(row) && isCard && nestedCards && nestedCards.closeCard();
193
198
  }
194
- function visitRows(rows, addSpacer, level) {
199
+ function visitRows(rows, addSpacer, level, visible) {
195
200
  const length = rows.length;
196
201
  rows.forEach((row, i) => {
197
202
  if (row[0].kind === "header") {
@@ -202,15 +207,15 @@ function GridTable(props) {
202
207
  totalsRows.push([row[0], makeRowComponent(row[0], level)]);
203
208
  return;
204
209
  }
205
- visit(row, level);
206
- addSpacer && nestedCards && i !== length - 1 && nestedCards.addSpacer();
210
+ visit(row, level, visible);
211
+ visible && addSpacer && nestedCards && i !== length - 1 && nestedCards.addSpacer();
207
212
  });
208
213
  }
209
214
  // Call `visitRows` with our a pre-filtered set list
210
215
  // If nestedCards is set, we assume the top-level kind is a card, and so should add spacers between them
211
- visitRows(maybeSorted.reduce(filterRows, []), !!nestedCards, 0);
216
+ visitRows(maybeSorted.reduce(filterRows, []), !!nestedCards, 0, true);
212
217
  nestedCards && nestedCards.done();
213
- return [headerRows, filteredRows, totalsRows];
218
+ return [headerRows, visibleDataRows, totalsRows, filteredRowIds];
214
219
  }, [
215
220
  as,
216
221
  maybeSorted,
@@ -230,21 +235,21 @@ function GridTable(props) {
230
235
  hasTotalsRow,
231
236
  ]);
232
237
  let tooManyClientSideRows = false;
233
- if (filterMaxRows && filteredRows.length > filterMaxRows) {
238
+ if (filterMaxRows && visibleDataRows.length > filterMaxRows) {
234
239
  tooManyClientSideRows = true;
235
- filteredRows = filteredRows.slice(0, filterMaxRows);
240
+ visibleDataRows = visibleDataRows.slice(0, filterMaxRows);
236
241
  }
237
- rowState.setVisibleRows(filteredRows.map(([row]) => { var _a; return (_a = row === null || row === void 0 ? void 0 : row.id) !== null && _a !== void 0 ? _a : ""; }));
242
+ rowState.setMatchedRows(filteredRowIds);
238
243
  // Push back to the caller a way to ask us where a row is.
239
244
  const { rowLookup } = props;
240
245
  if (rowLookup) {
241
246
  // Refs are cheap to assign to, so we don't bother doing this in a useEffect
242
- rowLookup.current = (0, GridRowLookup_1.createRowLookup)(columns, filteredRows, virtuosoRef);
247
+ rowLookup.current = (0, GridRowLookup_1.createRowLookup)(columns, visibleDataRows, virtuosoRef);
243
248
  }
244
249
  (0, react_1.useEffect)(() => {
245
- setRowCount && (filteredRows === null || filteredRows === void 0 ? void 0 : filteredRows.length) !== undefined && setRowCount(filteredRows.length);
246
- }, [filteredRows === null || filteredRows === void 0 ? void 0 : filteredRows.length, setRowCount]);
247
- const noData = filteredRows.length === 0;
250
+ setRowCount && (visibleDataRows === null || visibleDataRows === void 0 ? void 0 : visibleDataRows.length) !== undefined && setRowCount(visibleDataRows.length);
251
+ }, [visibleDataRows === null || visibleDataRows === void 0 ? void 0 : visibleDataRows.length, setRowCount]);
252
+ const noData = visibleDataRows.length === 0;
248
253
  const firstRowMessage = (noData && fallbackMessage) || (tooManyClientSideRows && "Hiding some rows, use filter...") || infoMessage;
249
254
  const borderless = (_a = style === null || style === void 0 ? void 0 : style.presentationSettings) === null || _a === void 0 ? void 0 : _a.borderless;
250
255
  const typeScale = (_b = style === null || style === void 0 ? void 0 : style.presentationSettings) === null || _b === void 0 ? void 0 : _b.typeScale;
@@ -263,7 +268,7 @@ function GridTable(props) {
263
268
  // behave semantically the same as `as=div` did for its tests.
264
269
  const _as = as === "virtual" && runningInJest ? "div" : as;
265
270
  const rowStateContext = (0, react_1.useMemo)(() => ({ rowState }), [rowState]);
266
- return ((0, jsx_runtime_1.jsx)(RowState_1.RowStateContext.Provider, Object.assign({ value: rowStateContext }, { children: (0, jsx_runtime_1.jsxs)(PresentationContext_1.PresentationProvider, Object.assign({ fieldProps: fieldProps, wrap: (_c = style === null || style === void 0 ? void 0 : style.presentationSettings) === null || _c === void 0 ? void 0 : _c.wrap }, { children: [(0, jsx_runtime_1.jsx)("div", { ref: resizeRef, css: Css_1.Css.w100.$ }, void 0), renders[_as](style, id, columns, headerRows, totalsRows, filteredRows, firstRowMessage, stickyHeader, (_d = style.nestedCards) === null || _d === void 0 ? void 0 : _d.firstLastColumnWidth, xss, virtuosoRef)] }), void 0) }), void 0));
271
+ return ((0, jsx_runtime_1.jsx)(RowState_1.RowStateContext.Provider, Object.assign({ value: rowStateContext }, { children: (0, jsx_runtime_1.jsxs)(PresentationContext_1.PresentationProvider, Object.assign({ fieldProps: fieldProps, wrap: (_c = style === null || style === void 0 ? void 0 : style.presentationSettings) === null || _c === void 0 ? void 0 : _c.wrap }, { children: [(0, jsx_runtime_1.jsx)("div", { ref: resizeRef, css: Css_1.Css.w100.$ }, void 0), renders[_as](style, id, columns, headerRows, totalsRows, visibleDataRows, firstRowMessage, stickyHeader, (_d = style.nestedCards) === null || _d === void 0 ? void 0 : _d.firstLastColumnWidth, xss, virtuosoRef)] }), void 0) }), void 0));
267
272
  }
268
273
  exports.GridTable = GridTable;
269
274
  // Determine which HTML element to use to build the GridTable
@@ -273,7 +278,7 @@ const renders = {
273
278
  virtual: renderVirtual,
274
279
  };
275
280
  /** Renders table using divs with flexbox rows, which is the default render */
276
- function renderDiv(style, id, columns, headerRows, totalsRows, filteredRows, firstRowMessage, _stickyHeader, firstLastColumnWidth, xss, _virtuosoRef) {
281
+ function renderDiv(style, id, columns, headerRows, totalsRows, visibleDataRows, firstRowMessage, _stickyHeader, firstLastColumnWidth, xss, _virtuosoRef) {
277
282
  return ((0, jsx_runtime_1.jsxs)("div", Object.assign({ css: {
278
283
  // Use `fit-content` to ensure the width of the table takes up the full width of its content.
279
284
  // Otherwise, the table's width would be that of its container, which may not be as wide as the table itself.
@@ -290,10 +295,10 @@ function renderDiv(style, id, columns, headerRows, totalsRows, filteredRows, fir
290
295
  ...style.rootCss,
291
296
  ...(style.minWidthPx ? Css_1.Css.mwPx(style.minWidthPx).$ : {}),
292
297
  ...xss,
293
- }, "data-testid": id }, { children: [totalsRows.map(([, node]) => node), headerRows.map(([, node]) => node), firstRowMessage && ((0, jsx_runtime_1.jsx)("div", Object.assign({ css: { ...style.firstRowMessageCss }, "data-gridrow": true }, { children: firstRowMessage }), void 0)), filteredRows.map(([, node]) => node)] }), void 0));
298
+ }, "data-testid": id }, { children: [totalsRows.map(([, node]) => node), headerRows.map(([, node]) => node), firstRowMessage && ((0, jsx_runtime_1.jsx)("div", Object.assign({ css: { ...style.firstRowMessageCss }, "data-gridrow": true }, { children: firstRowMessage }), void 0)), visibleDataRows.map(([, node]) => node)] }), void 0));
294
299
  }
295
300
  /** Renders as a table, primarily/solely for good print support. */
296
- function renderTable(style, id, columns, headerRows, totalsRows, filteredRows, firstRowMessage, _stickyHeader, _firstLastColumnWidth, xss, _virtuosoRef) {
301
+ function renderTable(style, id, columns, headerRows, totalsRows, visibleDataRows, firstRowMessage, _stickyHeader, _firstLastColumnWidth, xss, _virtuosoRef) {
297
302
  return ((0, jsx_runtime_1.jsxs)("table", Object.assign({ css: {
298
303
  ...Css_1.Css.w100.add("borderCollapse", "collapse").$,
299
304
  ...Css_1.Css.addIn("& > tbody > tr ", style.betweenRowsCss || {})
@@ -302,7 +307,7 @@ function renderTable(style, id, columns, headerRows, totalsRows, filteredRows, f
302
307
  ...style.rootCss,
303
308
  ...(style.minWidthPx ? Css_1.Css.mwPx(style.minWidthPx).$ : {}),
304
309
  ...xss,
305
- }, "data-testid": id }, { children: [(0, jsx_runtime_1.jsx)("thead", { children: [...totalsRows, ...headerRows].map(([, node]) => node) }, void 0), (0, jsx_runtime_1.jsxs)("tbody", { children: [firstRowMessage && ((0, jsx_runtime_1.jsx)("tr", { children: (0, jsx_runtime_1.jsx)("td", Object.assign({ colSpan: columns.length, css: { ...style.firstRowMessageCss } }, { children: firstRowMessage }), void 0) }, void 0)), filteredRows.map(([, node]) => node)] }, void 0)] }), void 0));
310
+ }, "data-testid": id }, { children: [(0, jsx_runtime_1.jsx)("thead", { children: [...totalsRows, ...headerRows].map(([, node]) => node) }, void 0), (0, jsx_runtime_1.jsxs)("tbody", { children: [firstRowMessage && ((0, jsx_runtime_1.jsx)("tr", { children: (0, jsx_runtime_1.jsx)("td", Object.assign({ colSpan: columns.length, css: { ...style.firstRowMessageCss } }, { children: firstRowMessage }), void 0) }, void 0)), visibleDataRows.map(([, node]) => node)] }, void 0)] }), void 0));
306
311
  }
307
312
  /**
308
313
  * Uses react-virtuoso to render rows virtually.
@@ -324,7 +329,7 @@ function renderTable(style, id, columns, headerRows, totalsRows, filteredRows, f
324
329
  * [2]: https://github.com/tannerlinsley/react-virtual/issues/85
325
330
  * [3]: https://github.com/tannerlinsley/react-virtual/issues/108
326
331
  */
327
- function renderVirtual(style, id, columns, headerRows, totalsRows, filteredRows, firstRowMessage, stickyHeader, firstLastColumnWidth, xss, virtuosoRef) {
332
+ function renderVirtual(style, id, columns, headerRows, totalsRows, visibleDataRows, firstRowMessage, stickyHeader, firstLastColumnWidth, xss, virtuosoRef) {
328
333
  // eslint-disable-next-line react-hooks/rules-of-hooks
329
334
  const { footerStyle, listStyle } = (0, react_1.useMemo)(() => {
330
335
  var _a;
@@ -363,8 +368,8 @@ function renderVirtual(style, id, columns, headerRows, totalsRows, filteredRows,
363
368
  index--;
364
369
  }
365
370
  // Lastly render `filteredRow`
366
- return filteredRows[index][1];
367
- }, totalCount: (headerRows.length || 0) + (totalsRows.length || 0) + (firstRowMessage ? 1 : 0) + (filteredRows.length || 0) }, void 0));
371
+ return visibleDataRows[index][1];
372
+ }, totalCount: (headerRows.length || 0) + (totalsRows.length || 0) + (firstRowMessage ? 1 : 0) + (visibleDataRows.length || 0) }, void 0));
368
373
  }
369
374
  /**
370
375
  * A table might render two of these components to represent two virtual lists.
@@ -20,7 +20,7 @@ export declare class RowState {
20
20
  private readonly collapsedRows;
21
21
  private persistCollapse;
22
22
  private readonly selectedRows;
23
- private visibleRows;
23
+ private matchedRows;
24
24
  rows: GridDataRow<any>[];
25
25
  activeRowId: string | undefined;
26
26
  /**
@@ -29,14 +29,14 @@ export declare class RowState {
29
29
  constructor();
30
30
  loadCollapse(persistCollapse: string | undefined, rows: GridDataRow<any>[]): void;
31
31
  setRows(rows: GridDataRow<any>[]): void;
32
- setVisibleRows(rowIds: string[]): void;
32
+ setMatchedRows(rowIds: string[]): void;
33
33
  get selectedIds(): string[];
34
34
  getSelected(id: string): SelectedState;
35
35
  selectRow(id: string, selected: boolean): void;
36
36
  get collapsedIds(): string[];
37
37
  isCollapsed(id: string): boolean;
38
38
  toggleCollapsed(id: string): void;
39
- private getVisibleChildrenStates;
39
+ private getMatchedChildrenStates;
40
40
  private setNestedSelectedStates;
41
41
  }
42
42
  /** Provides a context for rows to access their table's `RowState`. */
@@ -30,8 +30,8 @@ class RowState {
30
30
  // A set of just row ids, i.e. not row.kind+row.id
31
31
  this.collapsedRows = new mobx_1.ObservableSet();
32
32
  this.selectedRows = new mobx_1.ObservableMap();
33
- // Set of just row ids. Keeps track of which rows are visible. Used to filter out non-visible rows from `selectedIds`
34
- this.visibleRows = new mobx_1.ObservableSet();
33
+ // Set of just row ids. Keeps track of which rows match the filter. Used to filter rows from `selectedIds`
34
+ this.matchedRows = new mobx_1.ObservableSet();
35
35
  // The current list of rows, basically a useRef.current. Not reactive.
36
36
  this.rows = [];
37
37
  // Keeps track of the 'active' row, formatted `${row.kind}_${row.id}`
@@ -42,11 +42,11 @@ class RowState {
42
42
  // We only shallow observe rows so that:
43
43
  // a) we don't deeply/needlessly proxy-ize a large Apollo fragment cache, but
44
44
  // b) if rows changes, we re-run computeds like getSelectedRows that may need to see the
45
- // updated _contents_ of a given row, even if our other selected/visible row states don't change.
45
+ // updated _contents_ of a given row, even if our other selected/matched row states don't change.
46
46
  // (as any b/c rows is private, so the mapped type doesn't see it)
47
47
  { rows: mobx_1.observable.shallow });
48
- // Whenever our `visibleRows` change (i.e. via filtering) then we need to re-derive header and parent rows' selected state.
49
- (0, mobx_1.reaction)(() => [...this.visibleRows.values()].sort(), () => {
48
+ // Whenever our `matchedRows` change (i.e. via filtering) then we need to re-derive header and parent rows' selected state.
49
+ (0, mobx_1.reaction)(() => [...this.matchedRows.values()].sort(), () => {
50
50
  const map = new Map();
51
51
  map.set("header", deriveParentSelected(this.rows.flatMap((row) => this.setNestedSelectedStates(row, map))));
52
52
  // Merge the changes back into the selected rows state
@@ -95,20 +95,20 @@ class RowState {
95
95
  // Finally replace our existing list of rows
96
96
  this.rows = rows;
97
97
  }
98
- setVisibleRows(rowIds) {
98
+ setMatchedRows(rowIds) {
99
99
  // ObservableSet doesn't seem to do a `diff` inside `replace` before firing
100
100
  // observers/reactions that watch it, which can lead to render loops with the
101
101
  // application page is observing `GridTableApi.getSelectedRows`, and merely
102
102
  // the act of rendering GridTable (w/o row changes) causes it's `useComputed`
103
103
  // to be triggered.
104
- if (!mobx_1.comparer.shallow(rowIds, [...this.visibleRows.values()])) {
105
- this.visibleRows.replace(rowIds);
104
+ if (!mobx_1.comparer.shallow(rowIds, [...this.matchedRows.values()])) {
105
+ this.matchedRows.replace(rowIds);
106
106
  }
107
107
  }
108
108
  get selectedIds() {
109
109
  // Return only ids that are fully checked, i.e. not partial
110
110
  const ids = [...this.selectedRows.entries()]
111
- .filter(([id, v]) => this.visibleRows.has(id) && v === "checked")
111
+ .filter(([id, v]) => this.matchedRows.has(id) && v === "checked")
112
112
  .map(([k]) => k);
113
113
  // Hide our header marker
114
114
  const headerIndex = ids.indexOf("header");
@@ -129,7 +129,7 @@ class RowState {
129
129
  // Just mash the header + all rows + children as selected
130
130
  const map = new Map();
131
131
  map.set("header", "checked");
132
- (0, visitor_1.visit)(this.rows, (row) => this.visibleRows.has(row.id) && row.kind !== "totals" && map.set(row.id, "checked"));
132
+ (0, visitor_1.visit)(this.rows, (row) => this.matchedRows.has(row.id) && row.kind !== "totals" && map.set(row.id, "checked"));
133
133
  this.selectedRows.replace(map);
134
134
  }
135
135
  else {
@@ -147,15 +147,15 @@ class RowState {
147
147
  }
148
148
  // Everything here & down is deterministically on/off
149
149
  const map = new Map();
150
- (0, visitor_1.visit)([curr.row], (row) => this.visibleRows.has(row.id) && map.set(row.id, selected ? "checked" : "unchecked"));
150
+ (0, visitor_1.visit)([curr.row], (row) => this.matchedRows.has(row.id) && map.set(row.id, selected ? "checked" : "unchecked"));
151
151
  // Now walk up the parents and see if they are now-all-checked/now-all-unchecked/some-of-each
152
152
  for (const parent of [...curr.parents].reverse()) {
153
153
  if (parent.children) {
154
- map.set(parent.id, deriveParentSelected(this.getVisibleChildrenStates(parent.children, map)));
154
+ map.set(parent.id, deriveParentSelected(this.getMatchedChildrenStates(parent.children, map)));
155
155
  }
156
156
  }
157
157
  // And do the header + top-level "children" as a final one-off
158
- map.set("header", deriveParentSelected(this.getVisibleChildrenStates(this.rows, map)));
158
+ map.set("header", deriveParentSelected(this.getMatchedChildrenStates(this.rows, map)));
159
159
  this.selectedRows.merge(map);
160
160
  }
161
161
  }
@@ -218,14 +218,14 @@ class RowState {
218
218
  sessionStorage.setItem(this.persistCollapse, JSON.stringify(collapsedIds));
219
219
  }
220
220
  }
221
- getVisibleChildrenStates(children, map) {
221
+ getMatchedChildrenStates(children, map) {
222
222
  return children
223
- .filter((row) => row.id !== "header" && this.visibleRows.has(row.id))
223
+ .filter((row) => row.id !== "header" && this.matchedRows.has(row.id))
224
224
  .map((row) => map.get(row.id) || this.getSelected(row.id));
225
225
  }
226
226
  // Recursively traverse through rows to determine selected state of parent rows based on children
227
227
  setNestedSelectedStates(row, map) {
228
- if (this.visibleRows.has(row.id)) {
228
+ if (this.matchedRows.has(row.id)) {
229
229
  if (!row.children) {
230
230
  return [this.getSelected(row.id)];
231
231
  }
@@ -2,7 +2,7 @@ import { ReactNode } from "react";
2
2
  import { Value } from "./";
3
3
  import { BeamSelectFieldBaseProps } from "./internal/SelectFieldBase";
4
4
  import { HasIdAndName, Optional } from "../types";
5
- export interface MultiSelectFieldProps<O, V extends Value> extends BeamSelectFieldBaseProps<O, V> {
5
+ export interface MultiSelectFieldProps<O, V extends Value> extends Exclude<BeamSelectFieldBaseProps<O, V>, "unsetLabel"> {
6
6
  /** Renders `opt` in the dropdown menu, defaults to the `getOptionLabel` prop. */
7
7
  getOptionMenuLabel?: (opt: O) => string | ReactNode;
8
8
  getOptionValue: (opt: O) => V;
@@ -4,7 +4,7 @@ import { HasIdAndName, Optional } from "../types";
4
4
  export interface SelectFieldProps<O, V extends Value> extends Omit<BeamSelectFieldBaseProps<O, V>, "values" | "onSelect"> {
5
5
  /** The current value; it can be `undefined`, even if `V` cannot be. */
6
6
  value: V | undefined;
7
- onSelect: (value: V, opt: O) => void;
7
+ onSelect: (value: V | undefined, opt: O | undefined) => void;
8
8
  }
9
9
  /**
10
10
  * Provides a non-native select/dropdown widget.
@@ -9,7 +9,8 @@ function SelectField(props) {
9
9
  options, onSelect, value, ...otherProps } = props;
10
10
  return ((0, jsx_runtime_1.jsx)(SelectFieldBase_1.SelectFieldBase, Object.assign({}, otherProps, { options: options, getOptionLabel: getOptionLabel, getOptionValue: getOptionValue, values: [value], onSelect: (values, options) => {
11
11
  if (values.length > 0 && options.length > 0) {
12
- onSelect && onSelect(values[0], options[0]);
12
+ const option = options[0];
13
+ onSelect(values[0], option === SelectFieldBase_1.unsetOption ? undefined : option);
13
14
  }
14
15
  } }), void 0));
15
16
  }
@@ -3,8 +3,8 @@ import { PresentationFieldProps } from "../../components/PresentationContext";
3
3
  import { Value } from "../Value";
4
4
  import { BeamFocusableProps } from "../../interfaces";
5
5
  export interface BeamSelectFieldBaseProps<O, V extends Value> extends BeamFocusableProps, PresentationFieldProps {
6
- /** Renders `opt` in the dropdown menu, defaults to the `getOptionLabel` prop. */
7
- getOptionMenuLabel?: (opt: O) => string | ReactNode;
6
+ /** Renders `opt` in the dropdown menu, defaults to the `getOptionLabel` prop. `isUnsetOpt` is only defined for single SelectField */
7
+ getOptionMenuLabel?: (opt: O, isUnsetOpt?: boolean) => string | ReactNode;
8
8
  getOptionValue: (opt: O) => V;
9
9
  getOptionLabel: (opt: O) => string;
10
10
  /** The current value; it can be `undefined`, even if `V` cannot be. */
@@ -12,12 +12,7 @@ export interface BeamSelectFieldBaseProps<O, V extends Value> extends BeamFocusa
12
12
  onSelect: (values: V[], opts: O[]) => void;
13
13
  multiselect?: boolean;
14
14
  disabledOptions?: V[];
15
- options: O[] | {
16
- initial: O[];
17
- load: () => Promise<{
18
- options: O[];
19
- }>;
20
- };
15
+ options: OptionsOrLoad<O>;
21
16
  /** Whether the field is disabled. If a ReactNode, it's treated as a "disabled reason" that's shown in a tooltip. */
22
17
  disabled?: boolean | ReactNode;
23
18
  required?: boolean;
@@ -39,6 +34,8 @@ export interface BeamSelectFieldBaseProps<O, V extends Value> extends BeamFocusa
39
34
  contrast?: boolean;
40
35
  /** Placeholder content */
41
36
  placeholder?: string;
37
+ /** Only used for Single Select Fields. If set, prepends an option with a `undefined` value at the top of the list */
38
+ unsetLabel?: string;
42
39
  }
43
40
  /**
44
41
  * Provides a non-native select/dropdown widget.
@@ -50,3 +47,12 @@ export interface BeamSelectFieldBaseProps<O, V extends Value> extends BeamFocusa
50
47
  * and so we cannot easily change them.
51
48
  */
52
49
  export declare function SelectFieldBase<O, V extends Value>(props: BeamSelectFieldBaseProps<O, V>): JSX.Element;
50
+ declare type OptionsOrLoad<O> = O[] | {
51
+ initial: O[];
52
+ load: () => Promise<{
53
+ options: O[];
54
+ }>;
55
+ };
56
+ export declare function initializeOptions<O>(options: OptionsOrLoad<O>, unsetLabel: string | undefined): OptionsOrLoad<O>;
57
+ export declare const unsetOption: {};
58
+ export {};
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SelectFieldBase = void 0;
3
+ exports.unsetOption = exports.initializeOptions = exports.SelectFieldBase = 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");
@@ -23,7 +23,15 @@ const utils_1 = require("../../utils");
23
23
  */
24
24
  function SelectFieldBase(props) {
25
25
  var _a;
26
- const { disabled, readOnly, onSelect, options: maybeOptions, multiselect = false, getOptionLabel, getOptionValue, getOptionMenuLabel = getOptionLabel, values = [], nothingSelectedText = "", contrast, disabledOptions, borderless, ...otherProps } = props;
26
+ const { disabled, readOnly, onSelect, options, multiselect = false, values = [], nothingSelectedText = "", contrast, disabledOptions, borderless, unsetLabel, ...otherProps } = props;
27
+ // Call `initializeOptions` to prepend the `unset` option if the `unsetLabel` was provided.
28
+ const maybeOptions = (0, react_1.useMemo)(() => initializeOptions(options, unsetLabel), [options, unsetLabel]);
29
+ // Memoize the callback functions and handle the `unset` option if provided.
30
+ const getOptionLabel = (0, react_1.useCallback)((o) => (unsetLabel && o === exports.unsetOption ? unsetLabel : props.getOptionLabel(o)), [props.getOptionLabel, unsetLabel]);
31
+ const getOptionValue = (0, react_1.useCallback)((o) => (unsetLabel && o === exports.unsetOption ? undefined : props.getOptionValue(o)), [props.getOptionValue, unsetLabel]);
32
+ const getOptionMenuLabel = (0, react_1.useCallback)((o) => props.getOptionMenuLabel
33
+ ? props.getOptionMenuLabel(o, Boolean(unsetLabel) && o === exports.unsetOption)
34
+ : getOptionLabel(o), [props.getOptionValue, unsetLabel, getOptionLabel]);
27
35
  const { contains } = (0, react_aria_1.useFilter)({ sensitivity: "base" });
28
36
  const isDisabled = !!disabled;
29
37
  const isReadOnly = !!readOnly;
@@ -105,7 +113,9 @@ function SelectFieldBase(props) {
105
113
  async function maybeInitLoad() {
106
114
  if (!Array.isArray(maybeOptions)) {
107
115
  setFieldState((prevState) => ({ ...prevState, optionsLoading: true }));
108
- const { options } = await maybeOptions.load();
116
+ const loadedOptions = (await maybeOptions.load()).options;
117
+ // Ensure the `unset` option is prepended to the top of the list if `unsetLabel` was provided
118
+ const options = !unsetLabel ? loadedOptions : getOptionsWithUnset(unsetLabel, loadedOptions);
109
119
  setFieldState((prevState) => ({
110
120
  ...prevState,
111
121
  filteredOptions: options,
@@ -245,3 +255,17 @@ function getInputValue(selectedOptions, getOptionLabel, multiselect, nothingSele
245
255
  ? nothingSelectedText
246
256
  : "";
247
257
  }
258
+ function initializeOptions(options, unsetLabel) {
259
+ if (!unsetLabel) {
260
+ return options;
261
+ }
262
+ if (Array.isArray(options)) {
263
+ return getOptionsWithUnset(unsetLabel, options);
264
+ }
265
+ return { ...options, initial: getOptionsWithUnset(unsetLabel, options.initial) };
266
+ }
267
+ exports.initializeOptions = initializeOptions;
268
+ function getOptionsWithUnset(unsetLabel, options) {
269
+ return [exports.unsetOption, ...options];
270
+ }
271
+ exports.unsetOption = {};
package/dist/types.d.ts CHANGED
@@ -9,3 +9,4 @@ export declare type CanCloseCheck = {
9
9
  discardText?: string;
10
10
  continueText?: string;
11
11
  } | CheckFn;
12
+ export declare function assertNever(x: never): never;
package/dist/types.js CHANGED
@@ -1,2 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.assertNever = void 0;
4
+ function assertNever(x) {
5
+ throw new Error("Unexpected object: " + x);
6
+ }
7
+ exports.assertNever = assertNever;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homebound/beam",
3
- "version": "2.135.2",
3
+ "version": "2.137.0",
4
4
  "author": "Homebound",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -32,7 +32,7 @@
32
32
  "format": "prettier --loglevel warn --write \"**/*.{ts,tsx,css,md}\""
33
33
  },
34
34
  "dependencies": {
35
- "@homebound/form-state": "2.14.0",
35
+ "@homebound/form-state": "2.15.1",
36
36
  "@internationalized/number": "^3.0.3",
37
37
  "@react-aria/utils": "^3.11.3",
38
38
  "@react-hook/resize-observer": "^1.2.2",