@homebound/beam 4.56.3 → 4.57.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.d.cts CHANGED
@@ -8724,8 +8724,8 @@ type MenuSelectFieldBaseProps<O, V extends Value> = {
8724
8724
  getOptionValue?: (opt: O) => V;
8725
8725
  /** String used in the trigger and the option's accessible name. Defaults to `option.name`. */
8726
8726
  getOptionLabel?: (opt: O) => string;
8727
- /** Custom content for the menu row. Falls back to `getOptionLabel`. */
8728
- getOptionMenuLabel?: (opt: O) => ReactNode;
8727
+ /** Custom content for the menu row. Falls back to `getOptionLabel`. `isUnsetOpt` is true for the `unsetLabel` row. */
8728
+ getOptionMenuLabel?: (opt: O, isUnsetOpt: boolean) => ReactNode;
8729
8729
  /** Extra text the menu search matches, in addition to `getOptionLabel`. */
8730
8730
  getOptionSearchText?: (opt: O) => string | readonly string[];
8731
8731
  /** Sort options by `getOptionLabel`. On by default. `MenuTreeSelectField` defaults this to false. */
@@ -8756,6 +8756,8 @@ type MenuSelectFieldProps<O, V extends Value> = MenuSelectFieldBaseProps<O, V> &
8756
8756
  /** Value proposed by an AI model; puts the field in AI mode. */
8757
8757
  proposedValue?: V;
8758
8758
  onSelect: (value: V | undefined, opt: O | undefined) => void;
8759
+ /** `unsetLabel` row that clears the field. Stays first when options are sorted. Unlike `nothingSelectedText`, it is a selectable option. */
8760
+ unsetLabel?: string;
8759
8761
  };
8760
8762
  /** Which selected options to surface: every one, only leaves, or only the highest selected ancestors. */
8761
8763
  type TreeSelectionDisplay = "all" | "leaf" | "root";
package/dist/index.d.ts CHANGED
@@ -8724,8 +8724,8 @@ type MenuSelectFieldBaseProps<O, V extends Value> = {
8724
8724
  getOptionValue?: (opt: O) => V;
8725
8725
  /** String used in the trigger and the option's accessible name. Defaults to `option.name`. */
8726
8726
  getOptionLabel?: (opt: O) => string;
8727
- /** Custom content for the menu row. Falls back to `getOptionLabel`. */
8728
- getOptionMenuLabel?: (opt: O) => ReactNode;
8727
+ /** Custom content for the menu row. Falls back to `getOptionLabel`. `isUnsetOpt` is true for the `unsetLabel` row. */
8728
+ getOptionMenuLabel?: (opt: O, isUnsetOpt: boolean) => ReactNode;
8729
8729
  /** Extra text the menu search matches, in addition to `getOptionLabel`. */
8730
8730
  getOptionSearchText?: (opt: O) => string | readonly string[];
8731
8731
  /** Sort options by `getOptionLabel`. On by default. `MenuTreeSelectField` defaults this to false. */
@@ -8756,6 +8756,8 @@ type MenuSelectFieldProps<O, V extends Value> = MenuSelectFieldBaseProps<O, V> &
8756
8756
  /** Value proposed by an AI model; puts the field in AI mode. */
8757
8757
  proposedValue?: V;
8758
8758
  onSelect: (value: V | undefined, opt: O | undefined) => void;
8759
+ /** `unsetLabel` row that clears the field. Stays first when options are sorted. Unlike `nothingSelectedText`, it is a selectable option. */
8760
+ unsetLabel?: string;
8759
8761
  };
8760
8762
  /** Which selected options to surface: every one, only leaves, or only the highest selected ancestors. */
8761
8763
  type TreeSelectionDisplay = "all" | "leaf" | "root";
package/dist/index.js CHANGED
@@ -18647,6 +18647,89 @@ import { trussProps as trussProps76 } from "@homebound/truss/runtime";
18647
18647
  // src/inputs/MenuSelectField/MenuSelectField.tsx
18648
18648
  import { useMemo as useMemo29 } from "react";
18649
18649
 
18650
+ // src/inputs/Value.ts
18651
+ var VALUE_PREFIX = "__VALUE--";
18652
+ function keyToValue(key) {
18653
+ if (typeof key === "number") {
18654
+ return key;
18655
+ } else if (typeof key === "string") {
18656
+ if (key === `${VALUE_PREFIX}null` || key === "__VALUE:null") {
18657
+ return null;
18658
+ } else if (key === `${VALUE_PREFIX}undefined` || key === "__VALUE:undefined") {
18659
+ return void 0;
18660
+ } else if (key.startsWith(`${VALUE_PREFIX}boolean--`) || key.startsWith("__VALUE:boolean:")) {
18661
+ const parts = key.includes("--boolean--") ? key.split("--") : key.split(":");
18662
+ return parts[parts.length - 1] === "true";
18663
+ } else if (key.startsWith(`${VALUE_PREFIX}number--`) || key.startsWith("__VALUE:number:")) {
18664
+ const parts = key.includes("--number--") ? key.split("--") : key.split(":");
18665
+ return Number(parts[parts.length - 1]);
18666
+ } else {
18667
+ return key;
18668
+ }
18669
+ } else {
18670
+ throw new Error(`Unsupported key ${key}`);
18671
+ }
18672
+ }
18673
+ function valueToKey(value) {
18674
+ if (typeof value === "string") {
18675
+ return value;
18676
+ } else if (typeof value === "number") {
18677
+ return `${VALUE_PREFIX}number--${value}`;
18678
+ } else if (typeof value === "boolean") {
18679
+ return `${VALUE_PREFIX}boolean--${value}`;
18680
+ } else if (value === null) {
18681
+ return `${VALUE_PREFIX}null`;
18682
+ } else if (value === void 0) {
18683
+ return `${VALUE_PREFIX}undefined`;
18684
+ } else {
18685
+ throw new Error(`Unsupported value ${value}`);
18686
+ }
18687
+ }
18688
+
18689
+ // src/inputs/internal/selectFieldHelpers.ts
18690
+ function initializeOptions(optionsOrLoad, getOptionValue, getOptionLabel, unsetLabel, autoSort) {
18691
+ const result = [];
18692
+ if (unsetLabel) {
18693
+ result.push(unsetOption);
18694
+ }
18695
+ const userOptions = [];
18696
+ if (Array.isArray(optionsOrLoad)) {
18697
+ userOptions.push(...optionsOrLoad);
18698
+ } else {
18699
+ const { options, current: current2 } = optionsOrLoad;
18700
+ if (options) {
18701
+ userOptions.push(...options);
18702
+ }
18703
+ if (current2) {
18704
+ const toCheck = Array.isArray(current2) ? current2 : [current2];
18705
+ toCheck.forEach((current3) => {
18706
+ const value = getOptionValue(current3);
18707
+ const found = options && options.find((o) => getOptionValue(o) === value);
18708
+ if (!found) {
18709
+ userOptions.push(current3);
18710
+ }
18711
+ });
18712
+ }
18713
+ }
18714
+ result.push(...autoSort ? sortOptions(userOptions, getOptionLabel) : userOptions);
18715
+ return result;
18716
+ }
18717
+ var unsetOption = {};
18718
+ function disabledOptionToKeyedTuple(disabledOption) {
18719
+ if (typeof disabledOption === "object" && disabledOption !== null) {
18720
+ return [valueToKey(disabledOption.value), disabledOption.reason];
18721
+ } else {
18722
+ return [valueToKey(disabledOption), void 0];
18723
+ }
18724
+ }
18725
+ function sortOptions(options, getOptionLabel) {
18726
+ return [...options].sort((a, b) => {
18727
+ const labelA = getOptionLabel(a).toLowerCase();
18728
+ const labelB = getOptionLabel(b).toLowerCase();
18729
+ return labelA.localeCompare(labelB);
18730
+ });
18731
+ }
18732
+
18650
18733
  // src/inputs/MenuSelectField/internal/AnchoredOverlay.tsx
18651
18734
  import { useRef as useRef32 } from "react";
18652
18735
  import { FocusScope as FocusScope4 } from "react-aria";
@@ -19646,89 +19729,6 @@ import { useId as useId4, useLayoutEffect as useLayoutEffect8, useMemo as useMem
19646
19729
  import { ListKeyboardDelegate, useFilter as useFilter2, useFocusRing as useFocusRing10, useHover as useHover13 } from "react-aria";
19647
19730
  import { Item as Item2, useListState } from "react-stately";
19648
19731
 
19649
- // src/inputs/Value.ts
19650
- var VALUE_PREFIX = "__VALUE--";
19651
- function keyToValue(key) {
19652
- if (typeof key === "number") {
19653
- return key;
19654
- } else if (typeof key === "string") {
19655
- if (key === `${VALUE_PREFIX}null` || key === "__VALUE:null") {
19656
- return null;
19657
- } else if (key === `${VALUE_PREFIX}undefined` || key === "__VALUE:undefined") {
19658
- return void 0;
19659
- } else if (key.startsWith(`${VALUE_PREFIX}boolean--`) || key.startsWith("__VALUE:boolean:")) {
19660
- const parts = key.includes("--boolean--") ? key.split("--") : key.split(":");
19661
- return parts[parts.length - 1] === "true";
19662
- } else if (key.startsWith(`${VALUE_PREFIX}number--`) || key.startsWith("__VALUE:number:")) {
19663
- const parts = key.includes("--number--") ? key.split("--") : key.split(":");
19664
- return Number(parts[parts.length - 1]);
19665
- } else {
19666
- return key;
19667
- }
19668
- } else {
19669
- throw new Error(`Unsupported key ${key}`);
19670
- }
19671
- }
19672
- function valueToKey(value) {
19673
- if (typeof value === "string") {
19674
- return value;
19675
- } else if (typeof value === "number") {
19676
- return `${VALUE_PREFIX}number--${value}`;
19677
- } else if (typeof value === "boolean") {
19678
- return `${VALUE_PREFIX}boolean--${value}`;
19679
- } else if (value === null) {
19680
- return `${VALUE_PREFIX}null`;
19681
- } else if (value === void 0) {
19682
- return `${VALUE_PREFIX}undefined`;
19683
- } else {
19684
- throw new Error(`Unsupported value ${value}`);
19685
- }
19686
- }
19687
-
19688
- // src/inputs/internal/selectFieldHelpers.ts
19689
- function initializeOptions(optionsOrLoad, getOptionValue, getOptionLabel, unsetLabel, autoSort) {
19690
- const result = [];
19691
- if (unsetLabel) {
19692
- result.push(unsetOption);
19693
- }
19694
- const userOptions = [];
19695
- if (Array.isArray(optionsOrLoad)) {
19696
- userOptions.push(...optionsOrLoad);
19697
- } else {
19698
- const { options, current: current2 } = optionsOrLoad;
19699
- if (options) {
19700
- userOptions.push(...options);
19701
- }
19702
- if (current2) {
19703
- const toCheck = Array.isArray(current2) ? current2 : [current2];
19704
- toCheck.forEach((current3) => {
19705
- const value = getOptionValue(current3);
19706
- const found = options && options.find((o) => getOptionValue(o) === value);
19707
- if (!found) {
19708
- userOptions.push(current3);
19709
- }
19710
- });
19711
- }
19712
- }
19713
- result.push(...autoSort ? sortOptions(userOptions, getOptionLabel) : userOptions);
19714
- return result;
19715
- }
19716
- var unsetOption = {};
19717
- function disabledOptionToKeyedTuple(disabledOption) {
19718
- if (typeof disabledOption === "object" && disabledOption !== null) {
19719
- return [valueToKey(disabledOption.value), disabledOption.reason];
19720
- } else {
19721
- return [valueToKey(disabledOption), void 0];
19722
- }
19723
- }
19724
- function sortOptions(options, getOptionLabel) {
19725
- return [...options].sort((a, b) => {
19726
- const labelA = getOptionLabel(a).toLowerCase();
19727
- const labelB = getOptionLabel(b).toLowerCase();
19728
- return labelA.localeCompare(labelB);
19729
- });
19730
- }
19731
-
19732
19732
  // src/inputs/MenuSelectField/internal/menuTree.ts
19733
19733
  function buildMenuTree(roots, getKey, getChildren) {
19734
19734
  const nodes = /* @__PURE__ */ new Map();
@@ -19910,10 +19910,11 @@ import { jsx as jsx115 } from "react/jsx-runtime";
19910
19910
  function useMenuSelectField(props) {
19911
19911
  const {
19912
19912
  options: optionsProp,
19913
- getOptionValue = defaultOptionValue,
19914
- getOptionLabel = defaultOptionLabel,
19913
+ getOptionValue: propGetOptionValue = defaultOptionValue,
19914
+ getOptionLabel: propGetOptionLabel = defaultOptionLabel,
19915
19915
  getOptionMenuLabel,
19916
19916
  getOptionSearchText,
19917
+ unsetLabel,
19917
19918
  autoSort = true,
19918
19919
  onSearch,
19919
19920
  disabledOptions,
@@ -19956,11 +19957,13 @@ function useMenuSelectField(props) {
19956
19957
  const [search, setSearch] = useState31("");
19957
19958
  const [optionsLoading, setOptionsLoading] = useState31(false);
19958
19959
  const didRequestOptionsRef = useRef36(false);
19960
+ const getOptionLabel = (option) => unsetLabel && option === unsetOption ? unsetLabel : propGetOptionLabel(option);
19961
+ const getOptionValue = (option) => unsetLabel && option === unsetOption ? void 0 : propGetOptionValue(option);
19959
19962
  const options = useMemo28(
19960
- () => initializeOptions(optionsProp, getOptionValue, getOptionLabel, void 0, autoSort),
19963
+ () => initializeOptions(optionsProp, getOptionValue, getOptionLabel, unsetLabel, autoSort),
19961
19964
  // getOptionValue and getOptionLabel are typically inline lambdas
19962
19965
  // oxlint-disable-next-line react-hooks/exhaustive-deps
19963
- [optionsProp, autoSort]
19966
+ [optionsProp, autoSort, unsetLabel]
19964
19967
  );
19965
19968
  const { hoverProps, isHovered } = useHover13({});
19966
19969
  const { focusProps, isFocusVisible } = useFocusRing10();
@@ -20027,10 +20030,12 @@ function useMenuSelectField(props) {
20027
20030
  );
20028
20031
  }, [menuTree, allOptions, search, serverSearch, contains, expandedKeys, searchCollapsedKeys]);
20029
20032
  const filteredOptions = useMemo28(
20030
- () => menuTree || !search || serverSearch ? options : options.filter((o) => matchesSearch(o, search, getOptionLabel, getOptionSearchText, contains)),
20033
+ () => menuTree || !search || serverSearch ? options : options.filter(
20034
+ (option) => unsetLabel && option === unsetOption ? contains(getOptionLabel(option), search) : matchesSearch(option, search, getOptionLabel, getOptionSearchText, contains)
20035
+ ),
20031
20036
  // getOptionLabel and getOptionSearchText are typically inline lambdas
20032
20037
  // oxlint-disable-next-line react-hooks/exhaustive-deps
20033
- [menuTree, options, search, serverSearch, contains]
20038
+ [menuTree, options, search, serverSearch, contains, unsetLabel]
20034
20039
  );
20035
20040
  function onSelectionChange(keys) {
20036
20041
  if (keys === "all") return;
@@ -20080,7 +20085,7 @@ function useMenuSelectField(props) {
20080
20085
  }, [treeRows, filteredOptions]);
20081
20086
  const state = useListState({
20082
20087
  items: collectionItems,
20083
- children: (item) => /* @__PURE__ */ jsx115(Item2, { textValue: getOptionLabel(item.option), children: getOptionMenuLabel ? getOptionMenuLabel(item.option) : getOptionLabel(item.option) }, item.key),
20088
+ children: (item) => /* @__PURE__ */ jsx115(Item2, { textValue: getOptionLabel(item.option), children: getOptionMenuLabel ? getOptionMenuLabel(item.option, Boolean(unsetLabel) && item.option === unsetOption) : getOptionLabel(item.option) }, item.key),
20084
20089
  selectionMode: multiple ? "multiple" : "single",
20085
20090
  selectedKeys: selectedRowKeys,
20086
20091
  disabledKeys: disabledRowKeys,
@@ -20471,17 +20476,24 @@ function BaseMenuSelectField(props) {
20471
20476
  // src/inputs/MenuSelectField/MenuSelectField.tsx
20472
20477
  import { jsx as jsx117 } from "react/jsx-runtime";
20473
20478
  function MenuSelectField(props) {
20474
- const { value, proposedValue, onSelect, ...rest } = props;
20475
- const values = useMemo29(() => value === void 0 ? [] : [value], [value]);
20479
+ const { value, proposedValue, onSelect, unsetLabel, ...rest } = props;
20480
+ const values = useMemo29(() => {
20481
+ if (value !== void 0) return [value];
20482
+ return unsetLabel ? [void 0] : [];
20483
+ }, [value, unsetLabel]);
20476
20484
  const proposedValues = useMemo29(() => proposedValue === void 0 ? void 0 : [proposedValue], [proposedValue]);
20477
20485
  return /* @__PURE__ */ jsx117(
20478
20486
  BaseMenuSelectField,
20479
20487
  {
20480
20488
  ...rest,
20489
+ unsetLabel,
20481
20490
  selectionMode: "single",
20482
20491
  values,
20483
20492
  proposedValues,
20484
- onCommit: (next, opts) => onSelect(next[0], opts[0])
20493
+ onCommit: (next, opts) => {
20494
+ const option = opts[0];
20495
+ onSelect(next[0], option === unsetOption ? void 0 : option);
20496
+ }
20485
20497
  }
20486
20498
  );
20487
20499
  }
@@ -31499,7 +31511,6 @@ import { jsx as jsx241 } from "react/jsx-runtime";
31499
31511
  function singleFilter(props) {
31500
31512
  return (key) => new SingleFilter(key, props);
31501
31513
  }
31502
- var allOption = {};
31503
31514
  var SingleFilter = class extends BaseFilter {
31504
31515
  formatSelectedFilterLabel(value) {
31505
31516
  const { options } = this.props;
@@ -31507,26 +31518,15 @@ var SingleFilter = class extends BaseFilter {
31507
31518
  return resolveOptionSelectedFilterLabel(options, getOptionValue, getOptionLabel, value);
31508
31519
  }
31509
31520
  render(value, setValue, tid, inModal, vertical) {
31510
- const { label, defaultValue, options: maybeOptions, nothingSelectedText, autoSort, ...props } = this.props;
31511
- const { getOptionValue, getOptionLabel } = optionReaders2(this.props);
31512
- const unsetText = nothingSelectedText ?? "All";
31513
- const isStaticList = Array.isArray(maybeOptions);
31514
- const options = isStaticList ? [
31515
- allOption,
31516
- ...initializeOptions(maybeOptions, getOptionValue, getOptionLabel, void 0, autoSort ?? true)
31517
- ] : maybeOptions;
31521
+ const { label, defaultValue, nothingSelectedText, unsetLabel, ...props } = this.props;
31518
31522
  return /* @__PURE__ */ jsx241(
31519
31523
  MenuSelectField,
31520
31524
  {
31521
31525
  ...props,
31522
- autoSort: isStaticList ? false : autoSort,
31523
- options,
31524
- getOptionValue: (option) => option === allOption ? void 0 : getOptionValue(option),
31525
- getOptionLabel: (option) => option === allOption ? unsetText : getOptionLabel(option),
31526
+ unsetLabel: unsetLabel ?? nothingSelectedText ?? "All",
31526
31527
  value,
31527
31528
  label: this.label,
31528
31529
  labelStyle: inModal ? "hidden" : !inModal && !vertical ? "inline" : "above",
31529
- nothingSelectedText: unsetText,
31530
31530
  onSelect: (next) => setValue(next || void 0),
31531
31531
  ...this.testId(tid)
31532
31532
  }