@homebound/beam 3.61.0 → 3.63.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
@@ -9854,6 +9854,11 @@ type StepperTabProps = {
9854
9854
  disabled?: boolean;
9855
9855
  /** Collapses the tab down to its colored bottom border only, hiding the label — for the mobile view */
9856
9856
  collapsed?: boolean;
9857
+ /** Storybook-only visual state overrides for snapshotting pseudo-interactions. */
9858
+ __storyState?: {
9859
+ hovered?: boolean;
9860
+ focusVisible?: boolean;
9861
+ };
9857
9862
  };
9858
9863
  declare function StepperTab(props: StepperTabProps): JSX.Element;
9859
9864
 
@@ -10058,6 +10063,8 @@ type FormSectionLayoutProps = {
10058
10063
  description?: ReactNode;
10059
10064
  /** Rendered top-right of the title row, e.g. a "Save draft" button. */
10060
10065
  actions?: ButtonProps[];
10066
+ /** Rendered before form sections. Useful in forms that only need the main form title, or for form fields that shape the context of a form beforehand. */
10067
+ initialFields?: ReactNode;
10061
10068
  sections: FormSectionProps[];
10062
10069
  };
10063
10070
  /** Centered (720px) width/column shell for a form built out of `FormSection`s — e.g. as a `WorkflowLayoutStep`'s `content`. */
package/dist/index.d.ts CHANGED
@@ -9854,6 +9854,11 @@ type StepperTabProps = {
9854
9854
  disabled?: boolean;
9855
9855
  /** Collapses the tab down to its colored bottom border only, hiding the label — for the mobile view */
9856
9856
  collapsed?: boolean;
9857
+ /** Storybook-only visual state overrides for snapshotting pseudo-interactions. */
9858
+ __storyState?: {
9859
+ hovered?: boolean;
9860
+ focusVisible?: boolean;
9861
+ };
9857
9862
  };
9858
9863
  declare function StepperTab(props: StepperTabProps): JSX.Element;
9859
9864
 
@@ -10058,6 +10063,8 @@ type FormSectionLayoutProps = {
10058
10063
  description?: ReactNode;
10059
10064
  /** Rendered top-right of the title row, e.g. a "Save draft" button. */
10060
10065
  actions?: ButtonProps[];
10066
+ /** Rendered before form sections. Useful in forms that only need the main form title, or for form fields that shape the context of a form beforehand. */
10067
+ initialFields?: ReactNode;
10061
10068
  sections: FormSectionProps[];
10062
10069
  };
10063
10070
  /** Centered (720px) width/column shell for a form built out of `FormSection`s — e.g. as a `WorkflowLayoutStep`'s `content`. */
package/dist/index.js CHANGED
@@ -11762,7 +11762,6 @@ function TreeOption(props) {
11762
11762
  const {
11763
11763
  item,
11764
11764
  state,
11765
- allowCollapsing = true,
11766
11765
  disabledReason
11767
11766
  } = props;
11768
11767
  const leveledOption = item.value;
@@ -11791,7 +11790,7 @@ function TreeOption(props) {
11791
11790
  shouldFocusOnHover: false
11792
11791
  }, state, ref);
11793
11792
  const isGroup = groupKeys.includes(item.key);
11794
- const canCollapse = allowCollapsing && !!option.children?.length;
11793
+ const canCollapse = !!option.children?.length;
11795
11794
  function toggleCollapsed() {
11796
11795
  if (!canCollapse) return;
11797
11796
  setCollapsedKeys((prevKeys) => collapsedKeys.includes(item.key) ? prevKeys.filter((k) => k !== item.key) : [...prevKeys, item.key]);
@@ -11849,7 +11848,7 @@ function TreeOption(props) {
11849
11848
  ...isFocused && !isGroup ? listItemStyles.focus : {},
11850
11849
  ...isDisabled && !isGroup ? listItemStyles.disabled : {}
11851
11850
  }), children: [
11852
- allowCollapsing && /* @__PURE__ */ jsx52("span", { className: "w_18px fs0 df aic", children: canCollapse && /* @__PURE__ */ jsx52("button", { onClick: (e) => {
11851
+ /* @__PURE__ */ jsx52("span", { className: "w_18px fs0 df aic", children: canCollapse && /* @__PURE__ */ jsx52("button", { onClick: (e) => {
11853
11852
  e.preventDefault();
11854
11853
  e.stopPropagation();
11855
11854
  toggleCollapsed();
@@ -11908,16 +11907,7 @@ function isLeveledNode(node) {
11908
11907
  // src/inputs/internal/VirtualizedOptions.tsx
11909
11908
  import { jsx as jsx53 } from "react/jsx-runtime";
11910
11909
  function VirtualizedOptions(props) {
11911
- const {
11912
- state,
11913
- items,
11914
- onListHeightChange,
11915
- scrollOnFocus,
11916
- loading,
11917
- disabledOptionsWithReasons,
11918
- isTree,
11919
- allowCollapsing
11920
- } = props;
11910
+ const { state, items, onListHeightChange, scrollOnFocus, loading, disabledOptionsWithReasons, isTree } = props;
11921
11911
  const virtuosoRef = useRef21(null);
11922
11912
  const focusedKey = state.selectionManager.focusedKey;
11923
11913
  const focusedItem = focusedKey != null ? state.collection.getItem(focusedKey) : null;
@@ -11958,7 +11948,6 @@ function VirtualizedOptions(props) {
11958
11948
  {
11959
11949
  item,
11960
11950
  state,
11961
- allowCollapsing,
11962
11951
  disabledReason: disabledOptionsWithReasons[item.key]
11963
11952
  },
11964
11953
  item.key
@@ -12057,8 +12046,7 @@ function ListBox(props) {
12057
12046
  horizontalLayout = false,
12058
12047
  loading,
12059
12048
  disabledOptionsWithReasons = {},
12060
- isTree,
12061
- allowCollapsing
12049
+ isTree
12062
12050
  } = props;
12063
12051
  const {
12064
12052
  listBoxProps
@@ -12136,8 +12124,7 @@ function ListBox(props) {
12136
12124
  scrollOnFocus: props.shouldUseVirtualFocus,
12137
12125
  loading,
12138
12126
  disabledOptionsWithReasons,
12139
- isTree,
12140
- allowCollapsing
12127
+ isTree
12141
12128
  }
12142
12129
  ) })
12143
12130
  ] });
@@ -12230,7 +12217,8 @@ function TreeSelectFieldBase(props) {
12230
12217
  sensitivity: "base"
12231
12218
  });
12232
12219
  const {
12233
- collapsedKeys
12220
+ collapsedKeys,
12221
+ setCollapsedKeys
12234
12222
  } = useTreeSelectFieldProvider();
12235
12223
  const groupKeys = useMemo16(() => _groupOptions?.map((option) => valueToKey(option)) ?? [], [_groupOptions]);
12236
12224
  const groupKeySet = useMemo16(() => new Set(groupKeys), [groupKeys]);
@@ -12272,8 +12260,7 @@ function TreeSelectFieldBase(props) {
12272
12260
  selectedChipOptions: selectedChipState.options,
12273
12261
  allOptions: initialOptions,
12274
12262
  selectedOptionsLabels: selectedChipState.labels,
12275
- optionsLoading: false,
12276
- allowCollapsing: true
12263
+ optionsLoading: false
12277
12264
  };
12278
12265
  }, [initialOptions, values, chipDisplay, getOptionLabel, isReadOnly, nothingSelectedText, getOptionValue, groupKeySet]);
12279
12266
  const [fieldState, setFieldState] = useState18(() => initTreeFieldState());
@@ -12295,14 +12282,26 @@ function TreeSelectFieldBase(props) {
12295
12282
  setFieldState(initTreeFieldState());
12296
12283
  }
12297
12284
  }, [getOptionValue, initTreeFieldState, values]);
12285
+ const preSearchCollapsedKeys = useRef23(void 0);
12286
+ useEffect13(() => {
12287
+ if (fieldState.searchValue) {
12288
+ if (preSearchCollapsedKeys.current === void 0) {
12289
+ preSearchCollapsedKeys.current = collapsedKeys;
12290
+ if (collapsedKeys.length > 0) setCollapsedKeys([]);
12291
+ }
12292
+ } else if (preSearchCollapsedKeys.current !== void 0) {
12293
+ const keys = preSearchCollapsedKeys.current;
12294
+ preSearchCollapsedKeys.current = void 0;
12295
+ setCollapsedKeys(keys);
12296
+ }
12297
+ }, [fieldState.searchValue, collapsedKeys, setCollapsedKeys]);
12298
12298
  const filteredOptions = useMemo16(() => getFilteredOptions(fieldState.allOptions, fieldState.searchValue, collapsedKeys, contains, getOptionLabel, getOptionValue), [fieldState.allOptions, fieldState.searchValue, collapsedKeys, contains, getOptionLabel, getOptionValue]);
12299
12299
  const onInputChange = useCallback9((inputValue) => {
12300
12300
  setFieldState((prevState) => {
12301
12301
  return {
12302
12302
  ...prevState,
12303
12303
  inputValue,
12304
- searchValue: inputValue.length === 0 ? void 0 : inputValue,
12305
- allowCollapsing: inputValue.length === 0
12304
+ searchValue: inputValue.length === 0 ? void 0 : inputValue
12306
12305
  };
12307
12306
  });
12308
12307
  }, []);
@@ -12330,8 +12329,7 @@ function TreeSelectFieldBase(props) {
12330
12329
  setFieldState((prevState) => ({
12331
12330
  ...prevState,
12332
12331
  inputValue: "",
12333
- searchValue: void 0,
12334
- allowCollapsing: true
12332
+ searchValue: void 0
12335
12333
  }));
12336
12334
  }
12337
12335
  }
@@ -12366,9 +12364,8 @@ function TreeSelectFieldBase(props) {
12366
12364
  if (newKeys.size === 0) {
12367
12365
  setFieldState((prevState) => ({
12368
12366
  ...prevState,
12369
- inputValue: nothingSelectedText,
12370
- searchValue: void 0,
12371
- allowCollapsing: true,
12367
+ // Unselecting the last option shouldn't drop the search either
12368
+ inputValue: prevState.searchValue ?? nothingSelectedText,
12372
12369
  selectedKeys: [],
12373
12370
  selectedOptions: [],
12374
12371
  selectedChipOptions: [],
@@ -12442,9 +12439,9 @@ function TreeSelectFieldBase(props) {
12442
12439
  const selectedChipState = getSelectedChipState(fieldState.allOptions, selectedOptions, selectedKeys, chipDisplay, getOptionLabel, getOptionValue);
12443
12440
  setFieldState((prevState) => ({
12444
12441
  ...prevState,
12445
- // Since we reset the list of options upon selection changes, then set the `inputValue` to empty string to reflect that.
12446
- inputValue: "",
12447
- searchValue: void 0,
12442
+ // Keep any search in place, so picking several options out of the same filtered list doesn't
12443
+ // make the user retype it; when they're not searching the input stays empty as before.
12444
+ inputValue: prevState.searchValue ?? "",
12448
12445
  selectedKeys: [...selectedKeys],
12449
12446
  selectedOptions,
12450
12447
  selectedChipOptions: selectedChipState.options,
@@ -12476,8 +12473,7 @@ function TreeSelectFieldBase(props) {
12476
12473
  setFieldState((prevState) => ({
12477
12474
  ...prevState,
12478
12475
  inputValue: selectedOptions.length === 1 ? getOptionLabel(selectedOptions[0]) : selectedOptions.length === 0 ? nothingSelectedText : "",
12479
- searchValue: void 0,
12480
- allowCollapsing: true
12476
+ searchValue: void 0
12481
12477
  }));
12482
12478
  }
12483
12479
  }
@@ -12547,17 +12543,16 @@ function TreeSelectFieldBase(props) {
12547
12543
  onClose: () => state.toggle(),
12548
12544
  isOpen: state.isOpen,
12549
12545
  minWidth: 320,
12550
- children: /* @__PURE__ */ jsx57(ListBox, { ...listBoxProps, positionProps, state, listBoxRef, selectedOptions: fieldState.selectedChipOptions, getOptionLabel, getOptionValue: (o) => valueToKey(getOptionValue(o)), horizontalLayout: labelStyle === "left", loading: fieldState.optionsLoading, allowCollapsing: fieldState.allowCollapsing, disabledOptionsWithReasons, isTree: true })
12546
+ children: /* @__PURE__ */ jsx57(ListBox, { ...listBoxProps, positionProps, state, listBoxRef, selectedOptions: fieldState.selectedChipOptions, getOptionLabel, getOptionValue: (o) => valueToKey(getOptionValue(o)), horizontalLayout: labelStyle === "left", loading: fieldState.optionsLoading, disabledOptionsWithReasons, isTree: true })
12551
12547
  }
12552
12548
  )
12553
12549
  ] });
12554
12550
  }
12555
- function levelOptions(o, level, filtering, collapsedKeys, getOptionValue) {
12556
- const actualLevel = filtering ? 0 : level;
12551
+ function levelOptions(o, level, collapsedKeys, getOptionValue) {
12557
12552
  return [
12558
- [o, actualLevel],
12559
- // Flat map the children if the parent is not collapsed or if we are filtering (for the search results)
12560
- ...o.children?.length && (!collapsedKeys.includes(valueToKey(getOptionValue(o))) || filtering) ? o.children.flatMap((oc) => levelOptions(oc, actualLevel + 1, filtering, collapsedKeys, getOptionValue)) : []
12553
+ [o, level],
12554
+ // Flat map the children if the parent is not collapsed
12555
+ ...o.children?.length && !collapsedKeys.includes(valueToKey(getOptionValue(o))) ? o.children.flatMap((oc) => levelOptions(oc, level + 1, collapsedKeys, getOptionValue)) : []
12561
12556
  ];
12562
12557
  }
12563
12558
  function getTopLevelSelections(o, selectedKeys, getOptionValue) {
@@ -12566,7 +12561,16 @@ function getTopLevelSelections(o, selectedKeys, getOptionValue) {
12566
12561
  return [];
12567
12562
  }
12568
12563
  function getFilteredOptions(allOptions, searchValue, collapsedKeys, contains, getOptionLabel, getOptionValue) {
12569
- return allOptions.flatMap((option) => levelOptions(option, 0, !!searchValue, collapsedKeys, getOptionValue).filter(([nestedOption]) => searchValue ? contains(getOptionLabel(nestedOption), searchValue) : true));
12564
+ if (!searchValue) return allOptions.flatMap((option) => levelOptions(option, 0, collapsedKeys, getOptionValue));
12565
+ const search = searchValue;
12566
+ return allOptions.flatMap((option) => matchedOptions(option, 0, false));
12567
+ function matchedOptions(o, level, hasMatchedParent) {
12568
+ const isDirectlyMatched = contains(getOptionLabel(o), search);
12569
+ const children = o.children?.flatMap((oc) => matchedOptions(oc, level + 1, hasMatchedParent || isDirectlyMatched));
12570
+ if (!isDirectlyMatched && !hasMatchedParent && !children?.length) return [];
12571
+ const isCollapsed = collapsedKeys.includes(valueToKey(getOptionValue(o)));
12572
+ return isCollapsed ? [[o, level]] : [[o, level], ...children ?? []];
12573
+ }
12570
12574
  }
12571
12575
  function isOptionFullySelected(option, selectedKeys, disabledKeys, groupKeys, getOptionValue) {
12572
12576
  const key = valueToKey(getOptionValue(option));
@@ -24387,7 +24391,8 @@ function StepperTab(props) {
24387
24391
  completed,
24388
24392
  onClick,
24389
24393
  disabled = false,
24390
- collapsed = false
24394
+ collapsed = false,
24395
+ __storyState
24391
24396
  } = props;
24392
24397
  const ariaProps = {
24393
24398
  onPress: () => onClick(value),
@@ -24398,13 +24403,15 @@ function StepperTab(props) {
24398
24403
  buttonProps
24399
24404
  } = useButton13(ariaProps, ref);
24400
24405
  const {
24401
- isFocusVisible,
24406
+ isFocusVisible: isFocusVisibleFromEvents,
24402
24407
  focusProps
24403
24408
  } = useFocusRing18();
24404
24409
  const {
24405
24410
  hoverProps,
24406
- isHovered
24411
+ isHovered: isHoveredFromEvents
24407
24412
  } = useHover22(ariaProps);
24413
+ const isHovered = __storyState?.hovered ?? isHoveredFromEvents;
24414
+ const isFocusVisible = __storyState?.focusVisible ?? isFocusVisibleFromEvents;
24408
24415
  const tid = useTestIds(props, "stepperTab");
24409
24416
  return /* @__PURE__ */ jsxs99("button", { ref, ...mergeProps30(buttonProps, focusProps, hoverProps), "aria-label": label, ...trussProps104({
24410
24417
  ...stepperTabStyles.baseStyles,
@@ -24458,7 +24465,7 @@ function getCollapsedStyles(active, completed) {
24458
24465
  paddingTop: "pt0",
24459
24466
  paddingBottom: "pb0"
24460
24467
  },
24461
- ...completed && !active ? {
24468
+ ...active || completed ? {
24462
24469
  borderColor: "bcBlue600"
24463
24470
  } : {
24464
24471
  borderColor: "bcGray300"
@@ -25312,6 +25319,7 @@ function FormSectionLayout(props) {
25312
25319
  title,
25313
25320
  description,
25314
25321
  actions,
25322
+ initialFields,
25315
25323
  sections
25316
25324
  } = props;
25317
25325
  const tid = useTestIds(props, "formSectionLayout");
@@ -25330,6 +25338,7 @@ function FormSectionLayout(props) {
25330
25338
  ] }),
25331
25339
  actions && /* @__PURE__ */ jsx207("div", { className: "df gap1 fs0", ...tid.actions, children: actions.map((action) => /* @__PURE__ */ jsx207(Button, { ...action }, `${action.label}`)) })
25332
25340
  ] }),
25341
+ initialFields,
25333
25342
  /* @__PURE__ */ jsx207("div", { className: "df fdc gap6", children: sections.map((section, i) => /* @__PURE__ */ jsx207(FormSection, { ...section }, defaultTestId(section.title) || i)) })
25334
25343
  ] });
25335
25344
  }