@homebound/beam 2.90.4 → 2.91.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.
@@ -25,7 +25,8 @@ function ChipSelectField(props) {
25
25
  const isDisabled = !!disabled;
26
26
  const showClearButton = !disabled && clearable && !!value;
27
27
  const chipStyles = Css_1.Css[typeScale].tl.bgGray300.gray900.br16.pxPx(10).pyPx(2).$;
28
- const [isFocused, setIsFocused] = (0, react_1.useState)(false);
28
+ // Controls showing the focus border styles.
29
+ const [visualFocus, setVisualFocus] = (0, react_1.useState)(false);
29
30
  const [isClearFocused, setIsClearFocused] = (0, react_1.useState)(false);
30
31
  const { focusProps } = (0, react_aria_1.useFocus)({
31
32
  onFocus: (e) => {
@@ -35,10 +36,15 @@ function ChipSelectField(props) {
35
36
  }
36
37
  (0, utils_1.maybeCall)(onFocus);
37
38
  },
38
- // Do not call onBlur if we just opened the menu
39
- onBlur: () => !state.isOpen && (0, utils_1.maybeCall)(onBlur),
40
- // Do not change focus state if select menu is opened
41
- onFocusChange: (isFocused) => !state.isOpen && setIsFocused(isFocused),
39
+ onBlur: (e) => {
40
+ // Do not call onBlur if focus moved to within the Popover
41
+ if (popoverRef.current && popoverRef.current.contains(e.relatedTarget)) {
42
+ return;
43
+ }
44
+ (0, utils_1.maybeCall)(onBlur);
45
+ },
46
+ // Do not change visual focus state if select menu is opened
47
+ onFocusChange: (isFocused) => !state.isOpen && setVisualFocus(isFocused),
42
48
  });
43
49
  const { focusProps: clearFocusProps } = (0, react_aria_1.useFocus)({ onFocusChange: setIsClearFocused });
44
50
  const buttonRef = (0, react_1.useRef)(null);
@@ -72,12 +78,12 @@ function ChipSelectField(props) {
72
78
  isDisabled,
73
79
  items: listData.items,
74
80
  children: selectChildren,
81
+ autoFocus: true,
75
82
  };
76
83
  const state = (0, react_stately_1.useSelectState)({
77
84
  ...selectHookProps,
78
- autoFocus: false,
79
85
  selectedKey: (0, Value_1.valueToKey)(value),
80
- disallowEmptySelection: true,
86
+ disallowEmptySelection: false,
81
87
  onSelectionChange: (key) => {
82
88
  if (key === createNewOpt.id) {
83
89
  setShowInput(true);
@@ -87,11 +93,21 @@ function ChipSelectField(props) {
87
93
  if (selectedItem) {
88
94
  onSelect(key, selectedItem);
89
95
  }
96
+ // Per UX, when an option is selected then we want to call our `onBlur` callback and remove the focus styles. The field _is_ still in focus but that is only to retain tab position in the DOM.
97
+ // We cannot simply call `buttonRef.current.blur()` here because `state.isOpen === true` and we keep the visualFocus shown when the menu is open.
98
+ setVisualFocus(false);
99
+ (0, utils_1.maybeCall)(onBlur);
90
100
  },
91
101
  onOpenChange: (isOpen) => {
92
- if (!isOpen && buttonRef.current) {
93
- // When closing reset the focus to the button element.
94
- buttonRef.current.focus();
102
+ var _a;
103
+ if (!isOpen) {
104
+ // When closing, reset the focus to the button element. This is to retain "tab position" in the document, allowing hte user to hit "Tab" and move to the next tabbable element.
105
+ // If the menu closed due to a user selecting an option, then the field will not visually appear focused.
106
+ (_a = buttonRef.current) === null || _a === void 0 ? void 0 : _a.focus();
107
+ }
108
+ else {
109
+ // If opened, set visual focus to true. It is possible to be in a state where the browser focus is on this element, but we are not "visually" focused (see `onSelectionChange`). If the user opens the menu again, we should trigger the visual focus.
110
+ setVisualFocus(true);
95
111
  }
96
112
  },
97
113
  });
@@ -128,23 +144,21 @@ function ChipSelectField(props) {
128
144
  }, onBlur: removeCreateNewField }, tid.createNewField), void 0)), (0, jsx_runtime_1.jsxs)("div", Object.assign({ ref: wrapperRef, css: {
129
145
  ...chipStyles,
130
146
  ...Css_1.Css.dif.relative.p0.mwPx(32).if(!value).bgGray200.$,
131
- ...(isFocused ? Css_1.Css.bshFocus.$ : {}),
147
+ ...(visualFocus ? Css_1.Css.bshFocus.$ : {}),
132
148
  ...(showInput ? Css_1.Css.dn.$ : {}),
133
149
  } }, { children: [(0, jsx_runtime_1.jsx)(Label_1.Label, Object.assign({ label: label, labelProps: labelProps, hidden: true }, tid.label), void 0), (0, jsx_runtime_1.jsx)("button", Object.assign({}, (0, react_aria_1.mergeProps)(focusProps, buttonProps), { ref: buttonRef, css: {
134
150
  ...Css_1.Css.tl.br16.pxPx(10).pyPx(2).outline0.if(showClearButton).prPx(4).borderRadius("16px 0 0 16px").$,
135
151
  ...(isDisabled ? Css_1.Css.cursorNotAllowed.gray700.$ : {}),
136
152
  "&:hover:not(:disabled)": Css_1.Css.bgGray400.if(!value).bgGray300.$,
137
- }, title: state.selectedItem ? state.selectedItem.textValue : placeholder }, tid, { children: (0, jsx_runtime_1.jsx)("span", Object.assign({}, valueProps, { css: Css_1.Css.lineClamp1.breakAll.$ }, { children: state.selectedItem ? state.selectedItem.textValue : placeholder }), void 0) }), void 0), showClearButton && (
138
- // Apply a tabIndex=-1 to remove need for addresses this focus behavior separately from the rest of the button.
139
- // This will require the user to click on the button if they want to remove it.
140
- (0, jsx_runtime_1.jsx)("button", Object.assign({}, clearFocusProps, { css: {
153
+ }, title: state.selectedItem ? state.selectedItem.textValue : placeholder }, tid, { children: (0, jsx_runtime_1.jsx)("span", Object.assign({}, valueProps, { css: Css_1.Css.lineClamp1.breakAll.$ }, { children: state.selectedItem ? state.selectedItem.textValue : placeholder }), void 0) }), void 0), showClearButton && ((0, jsx_runtime_1.jsx)("button", Object.assign({}, clearFocusProps, { css: {
141
154
  ...Css_1.Css.prPx(4).borderRadius("0 16px 16px 0").outline0.$,
142
155
  "&:hover": Css_1.Css.bgGray400.$,
143
156
  ...(isClearFocused ? Css_1.Css.boxShadow(`0px 0px 0px 2px rgba(3,105,161,1)`).$ : {}),
144
157
  }, onClick: () => {
145
158
  onSelect(undefined, undefined);
159
+ (0, utils_1.maybeCall)(onBlur);
146
160
  setIsClearFocused(false);
147
- }, "aria-label": "Remove" }, tid.clearButton, { children: (0, jsx_runtime_1.jsx)(components_1.Icon, { icon: "x", inc: typeScale === "xs" ? 2 : undefined }, void 0) }), void 0))] }), void 0), state.isOpen && ((0, jsx_runtime_1.jsx)(internal_1.Popover, Object.assign({ triggerRef: buttonRef, popoverRef: popoverRef, positionProps: overlayProps, onClose: state.close, isOpen: state.isOpen, shouldCloseOnBlur: true }, { children: (0, jsx_runtime_1.jsx)(ListBox_1.ListBox, Object.assign({}, menuProps, { listBoxRef: listBoxRef, state: state, getOptionLabel: getOptionLabel, getOptionValue: getOptionValue, positionProps: overlayProps }), void 0) }), void 0))] }, void 0));
161
+ }, "aria-label": "Remove" }, tid.clearButton, { children: (0, jsx_runtime_1.jsx)(components_1.Icon, { icon: "x", inc: typeScale === "xs" ? 2 : undefined }, void 0) }), void 0))] }), void 0), state.isOpen && ((0, jsx_runtime_1.jsx)(internal_1.Popover, Object.assign({ triggerRef: buttonRef, popoverRef: popoverRef, positionProps: overlayProps, onClose: state.close, isOpen: state.isOpen, shouldCloseOnBlur: true }, { children: (0, jsx_runtime_1.jsx)(ListBox_1.ListBox, Object.assign({}, menuProps, { listBoxRef: listBoxRef, state: state, getOptionLabel: getOptionLabel, getOptionValue: getOptionValue, positionProps: overlayProps, positionOffset: 8 }), void 0) }), void 0))] }, void 0));
148
162
  const tooltipText = selectHookProps.isDisabled && typeof disabled !== "boolean" ? disabled : undefined;
149
163
  return tooltipText ? ((0, jsx_runtime_1.jsx)(components_1.Tooltip, Object.assign({ title: tooltipText, placement: "top" }, { children: field }), void 0)) : (field);
150
164
  }
@@ -75,6 +75,13 @@ function DateField(props) {
75
75
  state.close();
76
76
  (0, utils_1.maybeCall)(onBlur);
77
77
  },
78
+ onKeyDown: (e) => {
79
+ var _a;
80
+ if (e.key === "Enter") {
81
+ // Blur the field when the user hits the enter key - as if they are "committing" the value and done with the field
82
+ (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.blur();
83
+ }
84
+ },
78
85
  }, inputRef);
79
86
  const { triggerProps, overlayProps } = (0, react_aria_1.useOverlayTrigger)({ type: "dialog" }, state, buttonRef);
80
87
  const { buttonProps } = (0, react_aria_1.useButton)({
@@ -47,6 +47,13 @@ function NumberField(props) {
47
47
  onBlur: () => {
48
48
  valueRef.current = { wip: false };
49
49
  },
50
+ onKeyDown: (e) => {
51
+ var _a;
52
+ if (e.key === "Enter") {
53
+ // Blur the field when the user hits the enter key - as if they are "committing" the value and done with the field
54
+ (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.blur();
55
+ }
56
+ },
50
57
  validationState: errorMsg !== undefined ? "invalid" : "valid",
51
58
  label: label,
52
59
  isDisabled: disabled,
@@ -43,9 +43,12 @@ function TextAreaField(props) {
43
43
  ...(preventNewLines
44
44
  ? {
45
45
  onKeyDown: (e) => {
46
+ var _a;
46
47
  // Prevent user from typing the new line character
47
- if (e.keyCode === 13) {
48
+ if (e.key === "Enter") {
48
49
  e.preventDefault();
50
+ // And then leave the field
51
+ (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.blur();
49
52
  }
50
53
  },
51
54
  onInput: (e) => {
@@ -16,7 +16,16 @@ function TextField(props) {
16
16
  value,
17
17
  };
18
18
  const inputRef = (0, react_1.useRef)(null);
19
- const { labelProps, inputProps } = (0, react_aria_1.useTextField)(textFieldProps, inputRef);
19
+ const { labelProps, inputProps } = (0, react_aria_1.useTextField)({
20
+ ...textFieldProps,
21
+ onKeyDown: (e) => {
22
+ var _a;
23
+ if (e.key === "Enter") {
24
+ // Blur the field when the user hits the enter key - as if they are "committing" the value and done with the field
25
+ (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.blur();
26
+ }
27
+ },
28
+ }, inputRef);
20
29
  // Construct our TextFieldApi to give access to some imperative methods
21
30
  if (api) {
22
31
  api.current = {
@@ -24,6 +24,7 @@ function SelectFieldBase(props) {
24
24
  const { compact, disabled: isDisabled = false, errorMsg, helperText, label, hideLabel, required, inlineLabel, readOnly: isReadOnly = false, onSelect, fieldDecoration, options, onBlur, onFocus, multiselect = false, getOptionLabel, getOptionValue, getOptionMenuLabel = getOptionLabel, sizeToContent = false, values, nothingSelectedText = "", contrast, disabledOptions, borderless, ...otherProps } = props;
25
25
  const { contains } = (0, react_aria_1.useFilter)({ sensitivity: "base" });
26
26
  function onSelectionChange(keys) {
27
+ var _a;
27
28
  // Close menu upon selection change only for Single selection mode
28
29
  if (!multiselect) {
29
30
  state.close();
@@ -70,6 +71,8 @@ function SelectFieldBase(props) {
70
71
  selectedKeys: [firstKey],
71
72
  selectedOptions: firstSelectedOption ? [firstSelectedOption] : [],
72
73
  });
74
+ // When a single select menu item changes, then blur the field
75
+ (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.blur();
73
76
  }
74
77
  selectionChanged && onSelect([...keys.values()].map(Value_1.keyToValue));
75
78
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homebound/beam",
3
- "version": "2.90.4",
3
+ "version": "2.91.0",
4
4
  "author": "Homebound",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",