@homebound/beam 4.61.0 → 4.62.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
@@ -19334,7 +19334,7 @@ function valueToKey(value) {
19334
19334
  }
19335
19335
 
19336
19336
  // src/inputs/internal/selectFieldHelpers.ts
19337
- function initializeOptions(optionsOrLoad, getOptionValue, getOptionLabel, unsetLabel, autoSort) {
19337
+ function initializeOptions(optionsOrLoad, getOptionValue, getOptionLabel, unsetLabel, autoSort, getChildren) {
19338
19338
  const result = [];
19339
19339
  if (unsetLabel) {
19340
19340
  result.push(unsetOption);
@@ -19351,8 +19351,8 @@ function initializeOptions(optionsOrLoad, getOptionValue, getOptionLabel, unsetL
19351
19351
  const toCheck = Array.isArray(current2) ? current2 : [current2];
19352
19352
  toCheck.forEach((current3) => {
19353
19353
  const value = getOptionValue(current3);
19354
- const found = options && options.find((o) => getOptionValue(o) === value);
19355
- if (!found) {
19354
+ const alreadyListed = options !== void 0 && optionsIncludeValue(options, value, getOptionValue, getChildren);
19355
+ if (!alreadyListed) {
19356
19356
  userOptions.push(current3);
19357
19357
  }
19358
19358
  });
@@ -19362,6 +19362,13 @@ function initializeOptions(optionsOrLoad, getOptionValue, getOptionLabel, unsetL
19362
19362
  return result;
19363
19363
  }
19364
19364
  var unsetOption = {};
19365
+ function optionsIncludeValue(options, value, getOptionValue, getChildren) {
19366
+ return options.some((option) => {
19367
+ if (getOptionValue(option) === value) return true;
19368
+ const children = getChildren?.(option);
19369
+ return children !== void 0 && optionsIncludeValue(children, value, getOptionValue, getChildren);
19370
+ });
19371
+ }
19365
19372
  function disabledOptionToKeyedTuple(disabledOption) {
19366
19373
  if (typeof disabledOption === "object" && disabledOption !== null) {
19367
19374
  return [valueToKey(disabledOption.value), disabledOption.reason];
@@ -20131,7 +20138,9 @@ function BaseOptionRow(props) {
20131
20138
  id,
20132
20139
  disabledReason,
20133
20140
  mixedLegendId,
20134
- variant
20141
+ variant,
20142
+ indent,
20143
+ isPathRoot
20135
20144
  } = props;
20136
20145
  const entry = item.value;
20137
20146
  const ref = (0, import_react83.useRef)(null);
@@ -20166,6 +20175,11 @@ function BaseOptionRow(props) {
20166
20175
  ref,
20167
20176
  ...(0, import_runtime86.trussProps)({
20168
20177
  ...rowCss,
20178
+ ...indent !== void 0 ? {
20179
+ paddingLeft: ["pl_var", {
20180
+ "--paddingLeft": `${indent}px`
20181
+ }]
20182
+ } : {},
20169
20183
  ...highlighted ? {
20170
20184
  backgroundColor: ["bgColor_var", {
20171
20185
  "--backgroundColor": "var(--b-surface-raised-hover)"
@@ -20182,7 +20196,17 @@ function BaseOptionRow(props) {
20182
20196
  children: [
20183
20197
  variant === "checkbox" && /* @__PURE__ */ (0, import_jsx_runtime115.jsx)(StyledCheckbox, { isSelected, isDisabled }),
20184
20198
  variant === "checkbox" && mixedLegendId && /* @__PURE__ */ (0, import_jsx_runtime115.jsx)(MixedDot, {}),
20185
- /* @__PURE__ */ (0, import_jsx_runtime115.jsx)("span", { className: "fg1 mw0", children: item.rendered }),
20199
+ /* @__PURE__ */ (0, import_jsx_runtime115.jsx)("span", { ...(0, import_runtime86.trussProps)({
20200
+ ...{
20201
+ flexGrow: "fg1",
20202
+ minWidth: "mw0"
20203
+ },
20204
+ ...isPathRoot ? {
20205
+ fontWeight: "fw6",
20206
+ fontSize: "fz_14px",
20207
+ lineHeight: "lh_20px"
20208
+ } : {}
20209
+ }), children: item.rendered }),
20186
20210
  variant === "check" && isSelected && /* @__PURE__ */ (0, import_jsx_runtime115.jsx)("span", { className: "df aic fs0", children: /* @__PURE__ */ (0, import_jsx_runtime115.jsx)(Icon, { icon: "check", color: isDisabled ? "--b-text-disabled" /* TextDisabled */ : "--b-selection-indicator" /* SelectionIndicator */ }) }),
20187
20211
  variant === "check" && mixedLegendId && // Centered in the check icon's 24px box.
20188
20212
  /* @__PURE__ */ (0, import_jsx_runtime115.jsx)("span", { className: "df aic jcc fs0 h_24px w_24px", children: /* @__PURE__ */ (0, import_jsx_runtime115.jsx)(MixedDot, {}) })
@@ -20192,6 +20216,21 @@ function BaseOptionRow(props) {
20192
20216
  });
20193
20217
  }
20194
20218
  function TreeOptionRow(props) {
20219
+ const {
20220
+ selectionMode,
20221
+ onExpandedChange,
20222
+ ...rowProps
20223
+ } = props;
20224
+ if (selectionMode === "single") return /* @__PURE__ */ (0, import_jsx_runtime115.jsx)(SingleTreeOptionRow, { ...rowProps });
20225
+ return /* @__PURE__ */ (0, import_jsx_runtime115.jsx)(MultiTreeOptionRow, { ...rowProps, selectionMode, onExpandedChange });
20226
+ }
20227
+ function SingleTreeOptionRow({
20228
+ row,
20229
+ ...rest
20230
+ }) {
20231
+ return /* @__PURE__ */ (0, import_jsx_runtime115.jsx)(BaseOptionRow, { ...rest, variant: "check", indent: rowPaddingX + row.level * singleTreeIndent, isPathRoot: row.level === 0 });
20232
+ }
20233
+ function MultiTreeOptionRow(props) {
20195
20234
  const {
20196
20235
  item,
20197
20236
  state,
@@ -20282,6 +20321,7 @@ function TreeOptionRow(props) {
20282
20321
  }
20283
20322
  var menuRowHeight = 40;
20284
20323
  var rowPaddingX = 16;
20324
+ var singleTreeIndent = 16;
20285
20325
  var treeIndent = 28;
20286
20326
  var rowCss = {
20287
20327
  display: "df",
@@ -20359,7 +20399,7 @@ function OptionList(props) {
20359
20399
  disabledReasons,
20360
20400
  initialIndex,
20361
20401
  loading,
20362
- rowVariant,
20402
+ selectionMode,
20363
20403
  tree,
20364
20404
  mixed
20365
20405
  } = menu;
@@ -20388,8 +20428,7 @@ function OptionList(props) {
20388
20428
  });
20389
20429
  }, [focusedKey, state.collection]);
20390
20430
  const [initialTop] = (0, import_react84.useState)(() => Math.max(0, Math.min(initialIndex, items.length - 1)));
20391
- const Row2 = rowVariant === "checkbox" ? MultiOptionRow : SingleOptionRow;
20392
- const isTree = rowVariant === "tree" && tree !== void 0;
20431
+ const Row2 = selectionMode === "multiple" ? MultiOptionRow : SingleOptionRow;
20393
20432
  const isTest = process.env.NODE_ENV === "test";
20394
20433
  return /* @__PURE__ */ (0, import_jsx_runtime116.jsx)("div", { ...listBoxProps, ...onKeyDown ? {
20395
20434
  onKeyDown,
@@ -20413,10 +20452,10 @@ function OptionList(props) {
20413
20452
  const item = items[index];
20414
20453
  const entry = item?.value;
20415
20454
  if (!item || !entry) return null;
20416
- const treeRow = isTree ? tree?.rows.get(String(item.key)) : void 0;
20455
+ const treeRow = tree?.rows.get(String(item.key));
20417
20456
  const mixedLegendId = mixed?.keys.has(entry.valueKey) ? mixed.legendId : void 0;
20418
20457
  if (treeRow && tree) {
20419
- return /* @__PURE__ */ (0, import_jsx_runtime116.jsx)(TreeOptionRow, { item, state, id: optionDomId(id, index), disabledReason: disabledReasons[entry.valueKey], mixedLegendId, row: treeRow, onExpandedChange: (expanded) => tree.setExpanded(String(item.key), expanded), ...tid });
20458
+ return /* @__PURE__ */ (0, import_jsx_runtime116.jsx)(TreeOptionRow, { item, state, id: optionDomId(id, index), disabledReason: disabledReasons[entry.valueKey], mixedLegendId, row: treeRow, selectionMode, onExpandedChange: (expanded) => tree.setExpanded(String(item.key), expanded), ...tid });
20420
20459
  }
20421
20460
  return /* @__PURE__ */ (0, import_jsx_runtime116.jsx)(Row2, { item, state, id: optionDomId(id, index), disabledReason: disabledReasons[entry.valueKey], mixedLegendId, ...tid });
20422
20461
  } }, isTest ? items.length : "virtuoso"),
@@ -20689,10 +20728,10 @@ function useMenuSelectField(props) {
20689
20728
  const getOptionLabel = (option) => unsetLabel && option === unsetOption ? unsetLabel : propGetOptionLabel(option);
20690
20729
  const getOptionValue = (option) => unsetLabel && option === unsetOption ? void 0 : propGetOptionValue(option);
20691
20730
  const options = (0, import_react85.useMemo)(
20692
- () => initializeOptions(optionsProp, getOptionValue, getOptionLabel, unsetLabel, autoSort),
20731
+ () => initializeOptions(optionsProp, getOptionValue, getOptionLabel, unsetLabel, autoSort, getChildren),
20693
20732
  // getOptionValue and getOptionLabel are typically inline lambdas
20694
20733
  // oxlint-disable-next-line react-hooks/exhaustive-deps
20695
- [optionsProp, autoSort, unsetLabel]
20734
+ [optionsProp, autoSort, unsetLabel, getChildren]
20696
20735
  );
20697
20736
  const { hoverProps, isHovered } = (0, import_react_aria37.useHover)({});
20698
20737
  const { focusProps, isFocusVisible } = (0, import_react_aria37.useFocusRing)();
@@ -20703,6 +20742,8 @@ function useMenuSelectField(props) {
20703
20742
  // oxlint-disable-next-line react-hooks/exhaustive-deps
20704
20743
  [options, getChildren]
20705
20744
  );
20745
+ const isSingleSelectTree = !multiple && menuTree !== void 0;
20746
+ const isMultiSelectTree = multiple && menuTree !== void 0;
20706
20747
  const allOptions = (0, import_react85.useMemo)(
20707
20748
  () => menuTree ? menuTree.orderedKeys.map((k) => menuTree.nodes.get(k).option) : options,
20708
20749
  [menuTree, options]
@@ -20721,12 +20762,17 @@ function useMenuSelectField(props) {
20721
20762
  const { effectiveValue, proposalProps } = useAiProposal(
20722
20763
  propValues,
20723
20764
  proposedValues,
20724
- (vs) => labelsFor(vs, allOptions, getOptionValue, getOptionLabel).join(", ")
20765
+ (vs) => labelsFor(
20766
+ vs,
20767
+ allOptions,
20768
+ getOptionValue,
20769
+ (option) => isSingleSelectTree && menuTree ? optionPathLabel(menuTree, valueToKey(getOptionValue(option)), getOptionLabel) ?? getOptionLabel(option) : getOptionLabel(option)
20770
+ ).join(", ")
20725
20771
  );
20726
20772
  const values = (0, import_react85.useMemo)(() => effectiveValue ?? [], [effectiveValue]);
20727
20773
  const selectedKeys = (0, import_react85.useMemo)(
20728
- () => menuTree ? normalizeTreeSelection(menuTree, values.map(valueToKey), disabledKeySet) : new Set(values.map(valueToKey)),
20729
- [menuTree, values, disabledKeySet]
20774
+ () => isMultiSelectTree && menuTree ? normalizeTreeSelection(menuTree, values.map(valueToKey), disabledKeySet) : new Set(values.map(valueToKey)),
20775
+ [isMultiSelectTree, menuTree, values, disabledKeySet]
20730
20776
  );
20731
20777
  const selectedRowKeys = (0, import_react85.useMemo)(
20732
20778
  () => menuTree ? new Set(menuTree.orderedKeys.filter((key) => selectedKeys.has(menuTree.nodes.get(key).valueKey))) : selectedKeys,
@@ -20770,10 +20816,11 @@ function useMenuSelectField(props) {
20770
20816
  return visibleNestedRows(
20771
20817
  roots,
20772
20818
  (node) => childNodes(menuTree, node),
20773
- (node) => isExpanded(node.key),
20819
+ // A single-select hierarchy has no chevrons, so every node stays visible.
20820
+ isMultiSelectTree ? (node) => isExpanded(node.key) : () => true,
20774
20821
  matchKeys ? (node) => matchKeys.has(node.valueKey) : void 0
20775
20822
  );
20776
- }, [menuTree, allOptions, search, serverSearch, contains, expandedKeys, searchCollapsedKeys]);
20823
+ }, [menuTree, isMultiSelectTree, allOptions, search, serverSearch, contains, expandedKeys, searchCollapsedKeys]);
20777
20824
  const filteredOptions = (0, import_react85.useMemo)(
20778
20825
  () => menuTree || !search || serverSearch ? options : options.filter(
20779
20826
  (option) => unsetLabel && option === unsetOption ? contains(getOptionLabel(option), search) : matchesSearch(option, search, getOptionLabel, getOptionSearchText, contains)
@@ -20784,7 +20831,7 @@ function useMenuSelectField(props) {
20784
20831
  );
20785
20832
  function onSelectionChange(keys) {
20786
20833
  if (keys === "all") return;
20787
- if (menuTree) {
20834
+ if (isMultiSelectTree && menuTree) {
20788
20835
  const added = [...keys].map(String).find((key2) => !selectedRowKeys.has(key2));
20789
20836
  const removed = [...selectedRowKeys].find((key2) => !keys.has(key2));
20790
20837
  const key = added ?? removed;
@@ -20801,7 +20848,7 @@ function useMenuSelectField(props) {
20801
20848
  close({ restoreFocus: true });
20802
20849
  return;
20803
20850
  }
20804
- const opt = optionsByKey.get(String(key));
20851
+ const opt = isSingleSelectTree && menuTree ? menuTree.nodes.get(String(key))?.option : optionsByKey.get(String(key));
20805
20852
  proposalProps.onUserEdit?.();
20806
20853
  onCommit(opt === void 0 ? [] : [getOptionValue(opt)], opt === void 0 ? [] : [opt]);
20807
20854
  close({ restoreFocus: true });
@@ -20927,7 +20974,7 @@ function useMenuSelectField(props) {
20927
20974
  switch (e.key) {
20928
20975
  case "ArrowRight":
20929
20976
  case "ArrowLeft":
20930
- onTreeArrow(e, current2);
20977
+ if (isMultiSelectTree) onTreeArrow(e, current2);
20931
20978
  break;
20932
20979
  case "ArrowDown":
20933
20980
  e.preventDefault();
@@ -20998,20 +21045,20 @@ function useMenuSelectField(props) {
20998
21045
  0,
20999
21046
  treeRows ? treeRows.findIndex(({ node }) => revealKeys.has(node.valueKey)) : filteredOptions.findIndex((o) => revealKeys.has(valueToKey(getOptionValue(o))))
21000
21047
  );
21001
- const selectedText = !multiple && selectedOptions[0] !== void 0 ? getOptionLabel(selectedOptions[0]) : "";
21048
+ const selectedText = !multiple && selectedOptions[0] !== void 0 ? isSingleSelectTree && menuTree ? optionPathLabel(menuTree, valueToKey(getOptionValue(selectedOptions[0])), getOptionLabel) ?? getOptionLabel(selectedOptions[0]) : getOptionLabel(selectedOptions[0]) : "";
21002
21049
  const hasSelection = menuTree ? selectedKeys.size > 0 : values.length > 0;
21003
21050
  const isMixed = dottedKeys.size > 0 && (multiple || !hasSelection);
21004
21051
  const displayText = isMixed ? mixedLabel : selectedText || (hasSelection ? "" : nothingSelectedText || placeholder || "");
21005
21052
  const isPlaceholder = !isMixed && !selectedText && !hasSelection && !nothingSelectedText && !!placeholder;
21006
- const selectedLabels = menuTree && tree && getChildren ? bucketTreeSelection(selectedOptions, getChildren)[tree.display].map((option) => ({
21053
+ const selectedLabels = isMultiSelectTree && getChildren && tree?.display ? bucketTreeSelection(selectedOptions, getChildren)[tree.display].map((option) => ({
21007
21054
  key: valueToKey(getOptionValue(option)),
21008
21055
  label: getOptionLabel(option)
21009
21056
  })) : multiple ? selectedOptions.map((o) => ({ key: valueToKey(getOptionValue(o)), label: getOptionLabel(o) })) : [];
21010
21057
  const badgeKeys = isMixed ? /* @__PURE__ */ new Set([...selectedLabels.map(({ key }) => key), ...dottedKeys]) : void 0;
21011
21058
  const badgeLabels = badgeKeys ? [...optionsByKey].filter(([key]) => badgeKeys.has(key)).map(([key, option]) => ({ key, label: getOptionLabel(option) })) : selectedLabels;
21012
21059
  const partialKeys = (0, import_react85.useMemo)(
21013
- () => menuTree ? partiallySelectedKeys(menuTree, selectedKeys) : void 0,
21014
- [menuTree, selectedKeys]
21060
+ () => isMultiSelectTree && menuTree ? partiallySelectedKeys(menuTree, selectedKeys) : void 0,
21061
+ [isMultiSelectTree, menuTree, selectedKeys]
21015
21062
  );
21016
21063
  const treeRowStates = treeRows ? new Map(
21017
21064
  treeRows.map(({ node, level }) => [
@@ -21019,15 +21066,15 @@ function useMenuSelectField(props) {
21019
21066
  {
21020
21067
  level,
21021
21068
  hasChildren: node.childKeys.length > 0,
21022
- isExpanded: node.childKeys.length > 0 && isExpanded(node.key),
21023
- isIndeterminate: !!partialKeys?.has(node.key)
21069
+ isExpanded: isMultiSelectTree ? node.childKeys.length > 0 && isExpanded(node.key) : true,
21070
+ isIndeterminate: isMultiSelectTree && !!partialKeys?.has(node.key)
21024
21071
  }
21025
21072
  ])
21026
21073
  ) : void 0;
21027
21074
  function deselect(key) {
21028
21075
  if (!multiple || disabledKeys.includes(key)) return;
21029
21076
  proposalProps.onUserEdit?.();
21030
- if (menuTree) {
21077
+ if (isMultiSelectTree && menuTree) {
21031
21078
  const toggledKeys = toggleTreeSelection(menuTree, selectedKeys, key, false, disabledKeySet);
21032
21079
  commitTree(toggledKeys, key);
21033
21080
  return;
@@ -21100,7 +21147,7 @@ function useMenuSelectField(props) {
21100
21147
  loading: optionsLoading,
21101
21148
  disabledReasons,
21102
21149
  initialIndex,
21103
- rowVariant: treeRowStates ? "tree" : multiple ? "checkbox" : "check",
21150
+ selectionMode,
21104
21151
  tree: treeRowStates ? { rows: treeRowStates, setExpanded: setTreeExpanded } : void 0,
21105
21152
  mixed: dottedKeys.size > 0 ? { keys: dottedKeys, legendId } : void 0,
21106
21153
  onKeyDown: onMenuKeyDown,
@@ -21170,6 +21217,17 @@ function labelsFor(values, options, getOptionValue, getOptionLabel) {
21170
21217
  }
21171
21218
  return labels;
21172
21219
  }
21220
+ function optionPathLabel(tree, valueKey, getOptionLabel) {
21221
+ const node = tree.orderedKeys.map((key) => tree.nodes.get(key)).find((entry) => entry.valueKey === valueKey);
21222
+ if (!node) return void 0;
21223
+ const labels = [];
21224
+ let current2 = node;
21225
+ while (current2) {
21226
+ labels.unshift(getOptionLabel(current2.option));
21227
+ current2 = current2.parentKey === void 0 ? void 0 : tree.nodes.get(current2.parentKey);
21228
+ }
21229
+ return labels.join(" > ");
21230
+ }
21173
21231
  function rootNodes(tree) {
21174
21232
  return tree.orderedKeys.filter((key) => tree.nodes.get(key).parentKey === void 0).map((key) => tree.nodes.get(key));
21175
21233
  }
@@ -21282,7 +21340,8 @@ function BaseMenuSelectField(props) {
21282
21340
  // src/inputs/MenuSelectField/MenuSelectField.tsx
21283
21341
  var import_jsx_runtime119 = require("react/jsx-runtime");
21284
21342
  function MenuSelectField(props) {
21285
- const { value, proposedValue, onSelect, unsetLabel, ...rest } = props;
21343
+ const { value, proposedValue, onSelect, unsetLabel, options, autoSort, ...rest } = props;
21344
+ const nested = optionsHaveChildren(options);
21286
21345
  const values = (0, import_react86.useMemo)(() => {
21287
21346
  if (value !== void 0) return [value];
21288
21347
  return unsetLabel ? [void 0] : [];
@@ -21292,10 +21351,13 @@ function MenuSelectField(props) {
21292
21351
  BaseMenuSelectField,
21293
21352
  {
21294
21353
  ...rest,
21354
+ options,
21295
21355
  unsetLabel,
21356
+ autoSort: autoSort ?? !nested,
21296
21357
  selectionMode: "single",
21297
21358
  values,
21298
21359
  proposedValues,
21360
+ tree: nested ? { getChildren: menuOptionChildren } : void 0,
21299
21361
  onCommit: (next, opts) => {
21300
21362
  const option = opts[0];
21301
21363
  onSelect(next[0], option === unsetOption ? void 0 : option);
@@ -21303,6 +21365,19 @@ function MenuSelectField(props) {
21303
21365
  }
21304
21366
  );
21305
21367
  }
21368
+ function optionsHaveChildren(options) {
21369
+ const list = Array.isArray(options) ? options : [
21370
+ ...options.current === void 0 ? [] : Array.isArray(options.current) ? options.current : [options.current],
21371
+ ...options.options ?? []
21372
+ ];
21373
+ return list.some((option) => isNestedOption(option) && (option.children?.length ?? 0) > 0);
21374
+ }
21375
+ function menuOptionChildren(option) {
21376
+ return isNestedOption(option) ? option.children : void 0;
21377
+ }
21378
+ function isNestedOption(option) {
21379
+ return typeof option === "object" && option !== null && "children" in option && Array.isArray(option.children);
21380
+ }
21306
21381
 
21307
21382
  // src/components/Filters/Filters.tsx
21308
21383
  var import_jsx_runtime120 = require("react/jsx-runtime");