@homebound/beam 4.43.0 → 4.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -152,6 +152,7 @@ __export(index_exports, {
152
152
  Loader: () => Loader,
153
153
  LoadingSkeleton: () => LoadingSkeleton,
154
154
  MaxLines: () => MaxLines,
155
+ MenuMultiSelectField: () => MenuMultiSelectField,
155
156
  MenuSelectField: () => MenuSelectField,
156
157
  ModalBanner: () => ModalBanner,
157
158
  ModalBody: () => ModalBody,
@@ -33217,25 +33218,28 @@ function MenuSelectTrigger(props) {
33217
33218
  label,
33218
33219
  ":"
33219
33220
  ] }),
33220
- /* @__PURE__ */ (0, import_jsx_runtime269.jsx)("span", { id: trigger.valueId, className: "df aic fg1 mw0 oh", children: trigger.isReadOnly && showProposal ? /* @__PURE__ */ (0, import_jsx_runtime269.jsx)("span", { className: "fg1 wsnw oh to_ellipsis", children: /* @__PURE__ */ (0, import_jsx_runtime269.jsx)(ProposedValue, { original: originalText, proposed: proposedText, ...tid.proposedValue }) }) : /* @__PURE__ */ (0, import_jsx_runtime269.jsx)("span", { ...(0, import_runtime176.trussProps)({
33221
- ...{
33222
- flexGrow: "fg1",
33223
- whiteSpace: "wsnw",
33224
- overflow: "oh",
33225
- textOverflow: "to_ellipsis",
33226
- color: ["color_var", {
33227
- "--color": (0, import_runtime176.maybeCssVar)(trigger.isPlaceholder ? inputPlaceholderColor : void 0)
33228
- }]
33229
- },
33230
- // A placeholder reads as hint text, so it stays at the field's regular weight.
33231
- ...!trigger.isPlaceholder ? selectedValueCss(labelStyle, trigger.isReadOnly) : {},
33232
- ...showProposal ? {
33233
- fontWeight: "fw6",
33234
- color: ["color_var", {
33235
- "--color": "var(--b-ai-field-fg)"
33236
- }]
33237
- } : {}
33238
- }), children: trigger.displayText }) }),
33221
+ /* @__PURE__ */ (0, import_jsx_runtime269.jsx)("span", { id: trigger.valueId, className: "df aic fg1 mw0 oh", children: trigger.isReadOnly && showProposal ? /* @__PURE__ */ (0, import_jsx_runtime269.jsx)("span", { className: "fg1 wsnw oh to_ellipsis", children: /* @__PURE__ */ (0, import_jsx_runtime269.jsx)(ProposedValue, { original: originalText, proposed: proposedText, ...tid.proposedValue }) }) : /* @__PURE__ */ (0, import_jsx_runtime269.jsxs)(import_jsx_runtime269.Fragment, { children: [
33222
+ trigger.selectedLabels.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime269.jsx)(Tooltip, { title: /* @__PURE__ */ (0, import_jsx_runtime269.jsx)(SelectedLabels, { labels: trigger.selectedLabels }), children: /* @__PURE__ */ (0, import_jsx_runtime269.jsx)("span", { className: "df aic fs0 mr1", children: /* @__PURE__ */ (0, import_jsx_runtime269.jsx)(CountBadge, { count: trigger.selectedLabels.length, ...tid.count }) }) }),
33223
+ /* @__PURE__ */ (0, import_jsx_runtime269.jsx)("span", { ...(0, import_runtime176.trussProps)({
33224
+ ...{
33225
+ flexGrow: "fg1",
33226
+ whiteSpace: "wsnw",
33227
+ overflow: "oh",
33228
+ textOverflow: "to_ellipsis",
33229
+ color: ["color_var", {
33230
+ "--color": (0, import_runtime176.maybeCssVar)(trigger.isPlaceholder ? inputPlaceholderColor : void 0)
33231
+ }]
33232
+ },
33233
+ // A placeholder reads as hint text, so it stays at the field's regular weight.
33234
+ ...!trigger.isPlaceholder ? selectedValueCss(labelStyle, trigger.isReadOnly) : {},
33235
+ ...showProposal ? {
33236
+ fontWeight: "fw6",
33237
+ color: ["color_var", {
33238
+ "--color": "var(--b-ai-field-fg)"
33239
+ }]
33240
+ } : {}
33241
+ }), children: trigger.displayText })
33242
+ ] }) }),
33239
33243
  errorInTooltip && errorMsg && showErrorAndHelper && /* @__PURE__ */ (0, import_jsx_runtime269.jsx)("span", { className: "df aic fs0 pl1", children: /* @__PURE__ */ (0, import_jsx_runtime269.jsx)(Icon, { icon: "error", color: "--b-danger" /* Danger */, tooltip: errorMsg }) }),
33240
33244
  !trigger.isReadOnly && /* @__PURE__ */ (0, import_jsx_runtime269.jsx)("span", { ...(0, import_runtime176.trussProps)({
33241
33245
  display: "df",
@@ -33287,6 +33291,14 @@ function MenuSelectTrigger(props) {
33287
33291
  ] });
33288
33292
  }
33289
33293
  var inputPlaceholderColor = "#9ca3af";
33294
+ function SelectedLabels({
33295
+ labels
33296
+ }) {
33297
+ return /* @__PURE__ */ (0, import_jsx_runtime269.jsx)("ul", { className: "mt0 mb0 mr0 ml0 pl2", children: labels.map(({
33298
+ key,
33299
+ label
33300
+ }) => /* @__PURE__ */ (0, import_jsx_runtime269.jsx)("li", { children: label }, key)) });
33301
+ }
33290
33302
 
33291
33303
  // src/inputs/MenuSelectField/internal/OptionList.tsx
33292
33304
  var import_interactions2 = require("@react-aria/interactions");
@@ -33299,12 +33311,19 @@ var import_react185 = require("react");
33299
33311
  var import_react_aria76 = require("react-aria");
33300
33312
  var import_runtime177 = require("@homebound/truss/runtime");
33301
33313
  var import_jsx_runtime270 = require("react/jsx-runtime");
33302
- function OptionRow(props) {
33314
+ function SingleOptionRow(props) {
33315
+ return /* @__PURE__ */ (0, import_jsx_runtime270.jsx)(BaseOptionRow, { ...props, variant: "check" });
33316
+ }
33317
+ function MultiOptionRow(props) {
33318
+ return /* @__PURE__ */ (0, import_jsx_runtime270.jsx)(BaseOptionRow, { ...props, variant: "checkbox" });
33319
+ }
33320
+ function BaseOptionRow(props) {
33303
33321
  const {
33304
33322
  item,
33305
33323
  state,
33306
33324
  id,
33307
- disabledReason
33325
+ disabledReason,
33326
+ variant
33308
33327
  } = props;
33309
33328
  const ref = (0, import_react185.useRef)(null);
33310
33329
  const tid = useTestIds(props, "option");
@@ -33350,8 +33369,9 @@ function OptionRow(props) {
33350
33369
  }),
33351
33370
  ...tid.option,
33352
33371
  children: [
33372
+ variant === "checkbox" && /* @__PURE__ */ (0, import_jsx_runtime270.jsx)(StyledCheckbox, { isSelected, isDisabled }),
33353
33373
  /* @__PURE__ */ (0, import_jsx_runtime270.jsx)("span", { className: "wsnw oh to_ellipsis fg1 mw0", children: item.rendered }),
33354
- isSelected && /* @__PURE__ */ (0, import_jsx_runtime270.jsx)("span", { className: "df aic fs0", children: /* @__PURE__ */ (0, import_jsx_runtime270.jsx)(Icon, { icon: "check", color: isDisabled ? "--b-text-disabled" /* TextDisabled */ : "--b-selection-indicator" /* SelectionIndicator */ }) })
33374
+ variant === "check" && isSelected && /* @__PURE__ */ (0, import_jsx_runtime270.jsx)("span", { className: "df aic fs0", children: /* @__PURE__ */ (0, import_jsx_runtime270.jsx)(Icon, { icon: "check", color: isDisabled ? "--b-text-disabled" /* TextDisabled */ : "--b-selection-indicator" /* SelectionIndicator */ }) })
33355
33375
  ]
33356
33376
  }
33357
33377
  )
@@ -33393,7 +33413,8 @@ function OptionList(props) {
33393
33413
  listId: id,
33394
33414
  disabledReasons,
33395
33415
  initialIndex,
33396
- loading
33416
+ loading,
33417
+ rowVariant
33397
33418
  } = menu;
33398
33419
  const tid = useTestIds(props, "optionList");
33399
33420
  const virtuosoRef = (0, import_react186.useRef)(null);
@@ -33432,6 +33453,7 @@ function OptionList(props) {
33432
33453
  const maxTop = Math.max(0, initialCount * menuRowHeight - scroller.clientHeight);
33433
33454
  setScrollTop(scroller, Math.min(initialTop * menuRowHeight, maxTop));
33434
33455
  }, [listRef, syncScroll, initialTop, initialCount]);
33456
+ const Row2 = rowVariant === "checkbox" ? MultiOptionRow : SingleOptionRow;
33435
33457
  const isTest = process.env.NODE_ENV === "test";
33436
33458
  return /* @__PURE__ */ (0, import_jsx_runtime271.jsx)("div", { ...listBoxProps, ...onKeyDown ? {
33437
33459
  onKeyDown,
@@ -33452,7 +33474,7 @@ function OptionList(props) {
33452
33474
  }, itemContent: (index) => {
33453
33475
  const item = items[index];
33454
33476
  if (!item) return null;
33455
- return /* @__PURE__ */ (0, import_jsx_runtime271.jsx)(OptionRow, { item, state, id: optionDomId(id, index), disabledReason: disabledReasons[String(item.key)], ...tid });
33477
+ return /* @__PURE__ */ (0, import_jsx_runtime271.jsx)(Row2, { item, state, id: optionDomId(id, index), disabledReason: disabledReasons[String(item.key)], ...tid });
33456
33478
  } }, isTest ? items.length : "virtuoso"),
33457
33479
  loading && /* @__PURE__ */ (0, import_jsx_runtime271.jsx)(LoadingDots, {})
33458
33480
  ] }) });
@@ -33500,10 +33522,12 @@ function useMenuSelectField(props) {
33500
33522
  readOnly,
33501
33523
  onFocus,
33502
33524
  onBlur,
33503
- value: propValue,
33504
- proposedValue,
33505
- onSelect
33525
+ selectionMode,
33526
+ values: propValues,
33527
+ proposedValues,
33528
+ onCommit
33506
33529
  } = props;
33530
+ const multiple = selectionMode === "multiple";
33507
33531
  const isDisabled = !!disabled;
33508
33532
  const isReadOnly = !!readOnly;
33509
33533
  const fieldId = (0, import_react187.useId)();
@@ -33535,11 +33559,17 @@ function useMenuSelectField(props) {
33535
33559
  // oxlint-disable-next-line react-hooks/exhaustive-deps
33536
33560
  [options]
33537
33561
  );
33538
- const { effectiveValue: value, proposalProps } = useAiProposal(propValue, proposedValue, (v) => {
33539
- const option = optionsByKey.get(valueToKey(v));
33540
- return option === void 0 ? void 0 : getOptionLabel(option);
33541
- });
33542
- const selectedKeys = (0, import_react187.useMemo)(() => new Set(value === void 0 ? [] : [valueToKey(value)]), [value]);
33562
+ const { effectiveValue, proposalProps } = useAiProposal(
33563
+ propValues,
33564
+ proposedValues,
33565
+ (vs) => labelsFor(vs, options, getOptionValue, getOptionLabel).join(", ")
33566
+ );
33567
+ const values = (0, import_react187.useMemo)(() => effectiveValue ?? [], [effectiveValue]);
33568
+ const selectedKeys = (0, import_react187.useMemo)(() => new Set(values.map(valueToKey)), [values]);
33569
+ const selectedOptions = (0, import_react187.useMemo)(
33570
+ () => values.map((v) => optionsByKey.get(valueToKey(v))).filter((o) => o !== void 0),
33571
+ [values, optionsByKey]
33572
+ );
33543
33573
  const filteredOptions = (0, import_react187.useMemo)(
33544
33574
  () => search ? options.filter((o) => matchesSearch(o, search, getOptionLabel, getOptionSearchText, contains)) : options,
33545
33575
  // getOptionLabel and getOptionSearchText are typically inline lambdas
@@ -33552,20 +33582,33 @@ function useMenuSelectField(props) {
33552
33582
  }, [disabledOptions]);
33553
33583
  function onSelectionChange(keys) {
33554
33584
  if (keys === "all") return;
33555
- const [key] = [...keys];
33556
- if (key === void 0) {
33585
+ if (!multiple) {
33586
+ const [key] = [...keys];
33587
+ if (key === void 0) {
33588
+ close({ restoreFocus: true });
33589
+ return;
33590
+ }
33591
+ const opt = optionsByKey.get(String(key));
33592
+ proposalProps.onUserEdit?.();
33593
+ onCommit(opt === void 0 ? [] : [getOptionValue(opt)], opt === void 0 ? [] : [opt]);
33557
33594
  close({ restoreFocus: true });
33558
33595
  return;
33559
33596
  }
33560
- const opt = optionsByKey.get(String(key));
33597
+ const next = values.filter((v) => keys.has(valueToKey(v)));
33598
+ for (const k of keys) {
33599
+ const opt = optionsByKey.get(String(k));
33600
+ if (opt !== void 0 && !selectedKeys.has(String(k))) next.push(getOptionValue(opt));
33601
+ }
33561
33602
  proposalProps.onUserEdit?.();
33562
- onSelect(opt === void 0 ? void 0 : getOptionValue(opt), opt);
33563
- close({ restoreFocus: true });
33603
+ onCommit(
33604
+ next,
33605
+ next.map((v) => optionsByKey.get(valueToKey(v))).filter((o) => o !== void 0)
33606
+ );
33564
33607
  }
33565
33608
  const state = (0, import_react_stately20.useListState)({
33566
33609
  items: filteredOptions,
33567
33610
  children: (o) => /* @__PURE__ */ (0, import_jsx_runtime272.jsx)(import_react_stately20.Item, { textValue: getOptionLabel(o), children: getOptionMenuLabel ? getOptionMenuLabel(o) : getOptionLabel(o) }, valueToKey(getOptionValue(o))),
33568
- selectionMode: "single",
33611
+ selectionMode: multiple ? "multiple" : "single",
33569
33612
  selectedKeys,
33570
33613
  disabledKeys,
33571
33614
  onSelectionChange
@@ -33610,8 +33653,8 @@ function useMenuSelectField(props) {
33610
33653
  selectionManager.setFocusedKey(null);
33611
33654
  selectionManager.setFocused(false);
33612
33655
  }, [isOpen2, showSearch]);
33613
- function onSearchChange(value2) {
33614
- setSearch(value2);
33656
+ function onSearchChange(value) {
33657
+ setSearch(value);
33615
33658
  selectionManager.setFocusedKey(null);
33616
33659
  selectionManager.setFocused(false);
33617
33660
  }
@@ -33692,10 +33735,10 @@ function useMenuSelectField(props) {
33692
33735
  0,
33693
33736
  filteredOptions.findIndex((o) => selectedKeys.has(valueToKey(getOptionValue(o))))
33694
33737
  );
33695
- const selectedOption = value === void 0 ? void 0 : optionsByKey.get(valueToKey(value));
33696
- const selectedText = selectedOption === void 0 ? "" : getOptionLabel(selectedOption);
33697
- const displayText = selectedText || nothingSelectedText || placeholder || "";
33698
- const isPlaceholder = !selectedText && !nothingSelectedText && !!placeholder;
33738
+ const selectedText = !multiple && selectedOptions[0] !== void 0 ? getOptionLabel(selectedOptions[0]) : "";
33739
+ const displayText = selectedText || (values.length > 0 ? "" : nothingSelectedText || placeholder || "");
33740
+ const isPlaceholder = !selectedText && values.length === 0 && !nothingSelectedText && !!placeholder;
33741
+ const selectedLabels = multiple ? selectedOptions.map((o) => ({ key: valueToKey(getOptionValue(o)), label: getOptionLabel(o) })) : [];
33699
33742
  const triggerProps = {
33700
33743
  isDisabled,
33701
33744
  isReadOnly,
@@ -33716,6 +33759,7 @@ function useMenuSelectField(props) {
33716
33759
  onBlur: onTriggerBlur,
33717
33760
  displayText,
33718
33761
  isPlaceholder,
33762
+ selectedLabels,
33719
33763
  proposalProps
33720
33764
  };
33721
33765
  const searchProps = {
@@ -33734,11 +33778,16 @@ function useMenuSelectField(props) {
33734
33778
  loading: optionsLoading,
33735
33779
  disabledReasons,
33736
33780
  initialIndex,
33781
+ rowVariant: multiple ? "checkbox" : "check",
33737
33782
  onKeyDown: onMenuKeyDown,
33738
33783
  activeDescendant
33739
33784
  };
33740
33785
  return { triggerRef, trigger: triggerProps, searchRef, search: searchProps, listRef, menu: menuProps };
33741
33786
  }
33787
+ function labelsFor(values, options, getOptionValue, getOptionLabel) {
33788
+ const keys = new Set(values.map(valueToKey));
33789
+ return options.filter((option) => keys.has(valueToKey(getOptionValue(option)))).map(getOptionLabel);
33790
+ }
33742
33791
  function matchesSearch(option, query, getOptionLabel, getOptionSearchText, contains) {
33743
33792
  if (contains(getOptionLabel(option), query)) return true;
33744
33793
  const extra = getOptionSearchText?.(option);
@@ -33770,7 +33819,15 @@ var MenuSelectKeyboardDelegate = class _MenuSelectKeyboardDelegate extends impor
33770
33819
  // src/inputs/MenuSelectField/internal/BaseMenuSelectField.tsx
33771
33820
  var import_jsx_runtime273 = require("react/jsx-runtime");
33772
33821
  function BaseMenuSelectField(props) {
33773
- const { label, searchPlaceholder } = props;
33822
+ const {
33823
+ label,
33824
+ searchPlaceholder,
33825
+ selectionMode: _selectionMode,
33826
+ values: _values,
33827
+ proposedValues: _proposedValues,
33828
+ onCommit: _onCommit,
33829
+ ...fieldProps
33830
+ } = props;
33774
33831
  const { triggerRef, trigger, searchRef, search, listRef, menu } = useMenuSelectField(props);
33775
33832
  const tid = useTestIds(props, defaultTestId(label));
33776
33833
  const { sm: isMobile } = useBreakpoint();
@@ -33803,7 +33860,7 @@ function BaseMenuSelectField(props) {
33803
33860
  }
33804
33861
  );
33805
33862
  return /* @__PURE__ */ (0, import_jsx_runtime273.jsxs)(import_jsx_runtime273.Fragment, { children: [
33806
- /* @__PURE__ */ (0, import_jsx_runtime273.jsx)(MenuSelectTrigger, { ...props, trigger, triggerRef }),
33863
+ /* @__PURE__ */ (0, import_jsx_runtime273.jsx)(MenuSelectTrigger, { ...fieldProps, label, trigger, triggerRef }),
33807
33864
  isMobile ? /* @__PURE__ */ (0, import_jsx_runtime273.jsx)(BottomSheet, { isOpen: menu.isOpen, onClose: () => menu.close({ restoreFocus: true }), label, ...tid, children: panel }) : /* @__PURE__ */ (0, import_jsx_runtime273.jsx)(
33808
33865
  AnchoredOverlay,
33809
33866
  {
@@ -33817,18 +33874,38 @@ function BaseMenuSelectField(props) {
33817
33874
  ] });
33818
33875
  }
33819
33876
 
33820
- // src/inputs/MenuSelectField/MenuSelectField.tsx
33877
+ // src/inputs/MenuSelectField/MenuMultiSelectField.tsx
33821
33878
  var import_jsx_runtime274 = require("react/jsx-runtime");
33879
+ function MenuMultiSelectField(props) {
33880
+ const { onSelect, ...rest } = props;
33881
+ return /* @__PURE__ */ (0, import_jsx_runtime274.jsx)(BaseMenuSelectField, { ...rest, selectionMode: "multiple", onCommit: onSelect });
33882
+ }
33883
+
33884
+ // src/inputs/MenuSelectField/MenuSelectField.tsx
33885
+ var import_react188 = require("react");
33886
+ var import_jsx_runtime275 = require("react/jsx-runtime");
33822
33887
  function MenuSelectField(props) {
33823
- return /* @__PURE__ */ (0, import_jsx_runtime274.jsx)(BaseMenuSelectField, { ...props });
33888
+ const { value, proposedValue, onSelect, ...rest } = props;
33889
+ const values = (0, import_react188.useMemo)(() => value === void 0 ? [] : [value], [value]);
33890
+ const proposedValues = (0, import_react188.useMemo)(() => proposedValue === void 0 ? void 0 : [proposedValue], [proposedValue]);
33891
+ return /* @__PURE__ */ (0, import_jsx_runtime275.jsx)(
33892
+ BaseMenuSelectField,
33893
+ {
33894
+ ...rest,
33895
+ selectionMode: "single",
33896
+ values,
33897
+ proposedValues,
33898
+ onCommit: (next, opts) => onSelect(next[0], opts[0])
33899
+ }
33900
+ );
33824
33901
  }
33825
33902
 
33826
33903
  // src/inputs/ToggleButton.tsx
33827
- var import_react188 = require("react");
33904
+ var import_react189 = require("react");
33828
33905
  var import_react_aria79 = require("react-aria");
33829
33906
  var import_react_stately21 = require("react-stately");
33830
33907
  var import_runtime179 = require("@homebound/truss/runtime");
33831
- var import_jsx_runtime275 = require("react/jsx-runtime");
33908
+ var import_jsx_runtime276 = require("react/jsx-runtime");
33832
33909
  function ToggleButton(props) {
33833
33910
  const {
33834
33911
  selected: isSelected = false,
@@ -33839,7 +33916,7 @@ function ToggleButton(props) {
33839
33916
  __storyState,
33840
33917
  ...otherProps
33841
33918
  } = props;
33842
- const [asyncInProgress, setAsyncInProgress] = (0, import_react188.useState)(false);
33919
+ const [asyncInProgress, setAsyncInProgress] = (0, import_react189.useState)(false);
33843
33920
  const isDisabled = !!disabled || asyncInProgress;
33844
33921
  const ariaProps = {
33845
33922
  "aria-label": label,
@@ -33858,8 +33935,8 @@ function ToggleButton(props) {
33858
33935
  return result;
33859
33936
  }
33860
33937
  });
33861
- const labelRef = (0, import_react188.useRef)(null);
33862
- const ref = (0, import_react188.useRef)(null);
33938
+ const labelRef = (0, import_react189.useRef)(null);
33939
+ const ref = (0, import_react189.useRef)(null);
33863
33940
  const tid = useTestIds(otherProps, label);
33864
33941
  const {
33865
33942
  isPressed: isPressedFromEvents,
@@ -33932,10 +34009,10 @@ function ToggleButton(props) {
33932
34009
  return maybeTooltip({
33933
34010
  title: tooltip,
33934
34011
  placement: "top",
33935
- children: /* @__PURE__ */ (0, import_jsx_runtime275.jsxs)("label", { ...labelAttrs, children: [
33936
- icon && /* @__PURE__ */ (0, import_jsx_runtime275.jsx)(Icon, { icon }),
34012
+ children: /* @__PURE__ */ (0, import_jsx_runtime276.jsxs)("label", { ...labelAttrs, children: [
34013
+ icon && /* @__PURE__ */ (0, import_jsx_runtime276.jsx)(Icon, { icon }),
33937
34014
  label,
33938
- /* @__PURE__ */ (0, import_jsx_runtime275.jsx)(import_react_aria79.VisuallyHidden, { children: /* @__PURE__ */ (0, import_jsx_runtime275.jsx)("input", { ref, ...inputProps, ...tid.value }) })
34015
+ /* @__PURE__ */ (0, import_jsx_runtime276.jsx)(import_react_aria79.VisuallyHidden, { children: /* @__PURE__ */ (0, import_jsx_runtime276.jsx)("input", { ref, ...inputProps, ...tid.value }) })
33939
34016
  ] })
33940
34017
  });
33941
34018
  }
@@ -34076,6 +34153,7 @@ var togglePressStyles = {
34076
34153
  Loader,
34077
34154
  LoadingSkeleton,
34078
34155
  MaxLines,
34156
+ MenuMultiSelectField,
34079
34157
  MenuSelectField,
34080
34158
  ModalBanner,
34081
34159
  ModalBody,