@homebound/beam 2.365.1 → 2.366.1

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.
@@ -2,7 +2,7 @@ import { ReactNode } from "react";
2
2
  import { Value } from "./";
3
3
  import { ComboBoxBaseProps } from "./internal/ComboBoxBase";
4
4
  import { HasIdAndName, Optional } from "../types";
5
- export interface MultiSelectFieldProps<O, V extends Value> extends Exclude<ComboBoxBaseProps<O, V>, "unsetLabel"> {
5
+ export interface MultiSelectFieldProps<O, V extends Value> extends Exclude<ComboBoxBaseProps<O, V>, "unsetLabel" | "addNew"> {
6
6
  /** Renders `opt` in the dropdown menu, defaults to the `getOptionLabel` prop. */
7
7
  getOptionMenuLabel?: (opt: O) => string | ReactNode;
8
8
  getOptionValue: (opt: O) => V;
@@ -89,10 +89,7 @@ function RichTextField(props) {
89
89
  // The <trix-editor /> web component's `trix-initialize` event may fire before a `useEffect` hook in the component is executed, making it difficult ot attach the event listener locally.
90
90
  window.addEventListener("trix-initialize", onEditorInit);
91
91
  return id;
92
- },
93
- // TODO: validate this eslint-disable. It was automatically ignored as part of https://app.shortcut.com/homebound-team/story/40033/enable-react-hooks-exhaustive-deps-for-react-projects
94
- // eslint-disable-next-line react-hooks/exhaustive-deps
95
- []);
92
+ }, [readOnly]);
96
93
  (0, react_2.useEffect)(() => {
97
94
  // If our value prop changes (without the change coming from us), reload it
98
95
  if (!readOnly && editor && value !== currentHtml.current) {
@@ -5,7 +5,7 @@ import { BeamFocusableProps } from "../../interfaces";
5
5
  /** Base props for either `SelectField` or `MultiSelectField`. */
6
6
  export interface ComboBoxBaseProps<O, V extends Value> extends BeamFocusableProps, PresentationFieldProps {
7
7
  /** Renders `opt` in the dropdown menu, defaults to the `getOptionLabel` prop. `isUnsetOpt` is only defined for single SelectField */
8
- getOptionMenuLabel?: (opt: O, isUnsetOpt?: boolean) => string | ReactNode;
8
+ getOptionMenuLabel?: (opt: O, isUnsetOpt?: boolean, isAddNewOption?: boolean) => string | ReactNode;
9
9
  getOptionValue: (opt: O) => V;
10
10
  getOptionLabel: (opt: O) => string;
11
11
  /** The current value; it can be `undefined`, even if `V` cannot be. */
@@ -53,6 +53,7 @@ export interface ComboBoxBaseProps<O, V extends Value> extends BeamFocusableProp
53
53
  hideErrorMessage?: boolean;
54
54
  multiline?: boolean;
55
55
  onSearch?: (search: string) => void;
56
+ onAddNew?: (v: string) => void;
56
57
  }
57
58
  /**
58
59
  * Provides a non-native select/dropdown widget that allows the user to type to filter the options.
@@ -74,9 +75,13 @@ export type OptionsOrLoad<O> = O[] | {
74
75
  options: O[] | undefined;
75
76
  };
76
77
  /** Transforms/simplifies `optionsOrLoad` into just options, with unsetLabel maybe added. */
77
- export declare function initializeOptions<O, V extends Value>(optionsOrLoad: OptionsOrLoad<O>, getOptionValue: (opt: O) => V, unsetLabel: string | undefined): O[];
78
+ export declare function initializeOptions<O, V extends Value>(optionsOrLoad: OptionsOrLoad<O>, getOptionValue: (opt: O) => V, unsetLabel: string | undefined, addNew: boolean): O[];
78
79
  /** A marker option to automatically add an "Unset" option to the start of options. */
79
80
  export declare const unsetOption: {};
81
+ export declare const addNewOption: {
82
+ id: string;
83
+ name: string;
84
+ };
80
85
  export declare function disabledOptionToKeyedTuple(disabledOption: Value | {
81
86
  value: Value;
82
87
  reason: string;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.disabledOptionToKeyedTuple = exports.unsetOption = exports.initializeOptions = exports.ComboBoxBase = void 0;
6
+ exports.disabledOptionToKeyedTuple = exports.addNewOption = exports.unsetOption = exports.initializeOptions = exports.ComboBoxBase = void 0;
7
7
  const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
8
8
  const react_1 = require("react");
9
9
  const react_aria_1 = require("react-aria");
@@ -30,27 +30,39 @@ const fast_deep_equal_1 = __importDefault(require("fast-deep-equal"));
30
30
  function ComboBoxBase(props) {
31
31
  var _a, _b, _c, _d, _e;
32
32
  const { fieldProps } = (0, PresentationContext_1.usePresentationContext)();
33
- const { disabled, readOnly, onSelect, options: propOptions, multiselect = false, values: propValues, nothingSelectedText = "", contrast, disabledOptions, borderless, unsetLabel, getOptionLabel: propOptionLabel, getOptionValue: propOptionValue, getOptionMenuLabel: propOptionMenuLabel, fullWidth = (_a = fieldProps === null || fieldProps === void 0 ? void 0 : fieldProps.fullWidth) !== null && _a !== void 0 ? _a : false, onSearch, ...otherProps } = props;
33
+ const { disabled, readOnly, onSelect, options: propOptions, multiselect = false, values: propValues, nothingSelectedText = "", contrast, disabledOptions, borderless, unsetLabel, getOptionLabel: propOptionLabel, getOptionValue: propOptionValue, getOptionMenuLabel: propOptionMenuLabel, fullWidth = (_a = fieldProps === null || fieldProps === void 0 ? void 0 : fieldProps.fullWidth) !== null && _a !== void 0 ? _a : false, onSearch, onAddNew, ...otherProps } = props;
34
34
  const labelStyle = (_c = (_b = otherProps.labelStyle) !== null && _b !== void 0 ? _b : fieldProps === null || fieldProps === void 0 ? void 0 : fieldProps.labelStyle) !== null && _c !== void 0 ? _c : "above";
35
35
  // Memoize the callback functions and handle the `unset` option if provided.
36
- const getOptionLabel = (0, react_1.useCallback)((o) => (unsetLabel && o === exports.unsetOption ? unsetLabel : propOptionLabel(o)),
36
+ const getOptionLabel = (0, react_1.useCallback)((o) => unsetLabel && o === exports.unsetOption
37
+ ? unsetLabel
38
+ : onAddNew && o === exports.addNewOption
39
+ ? exports.addNewOption.name
40
+ : propOptionLabel(o),
37
41
  // propOptionLabel is basically always a lambda, so don't dep on it
38
42
  // eslint-disable-next-line react-hooks/exhaustive-deps
39
43
  [unsetLabel]);
40
- const getOptionValue = (0, react_1.useCallback)((o) => (unsetLabel && o === exports.unsetOption ? undefined : propOptionValue(o)),
44
+ const getOptionValue = (0, react_1.useCallback)((o) => unsetLabel && o === exports.unsetOption
45
+ ? undefined
46
+ : onAddNew && o === exports.addNewOption
47
+ ? exports.addNewOption.id
48
+ : propOptionValue(o),
41
49
  // propOptionValue is basically always a lambda, so don't dep on it
42
50
  // eslint-disable-next-line react-hooks/exhaustive-deps
43
51
  [unsetLabel]);
44
- const getOptionMenuLabel = (0, react_1.useCallback)((o) => propOptionMenuLabel ? propOptionMenuLabel(o, Boolean(unsetLabel) && o === exports.unsetOption) : getOptionLabel(o),
52
+ const getOptionMenuLabel = (0, react_1.useCallback)((o) => propOptionMenuLabel
53
+ ? propOptionMenuLabel(o, Boolean(unsetLabel) && o === exports.unsetOption, Boolean(onAddNew) && o === exports.addNewOption)
54
+ : getOptionLabel(o),
45
55
  // propOptionMenuLabel is basically always a lambda, so don't dep on it
46
56
  // eslint-disable-next-line react-hooks/exhaustive-deps
47
57
  [unsetLabel, getOptionLabel]);
48
58
  // Call `initializeOptions` to prepend the `unset` option if the `unsetLabel` was provided.
49
- const options = (0, react_1.useMemo)(() => initializeOptions(propOptions, getOptionValue, unsetLabel),
59
+ const options = (0, react_1.useMemo)(() => initializeOptions(propOptions, getOptionValue, unsetLabel, !!onAddNew),
50
60
  // If the caller is using { current, load, options }, memoize on only `current` and `options` values.
51
61
  // ...and don't bother on memoizing on getOptionValue b/c it's basically always a lambda
52
62
  // eslint-disable-next-line react-hooks/exhaustive-deps
53
- Array.isArray(propOptions) ? [propOptions, unsetLabel] : [propOptions.current, propOptions.options, unsetLabel]);
63
+ Array.isArray(propOptions)
64
+ ? [propOptions, unsetLabel, onAddNew]
65
+ : [propOptions.current, propOptions.options, unsetLabel, onAddNew]);
54
66
  const values = (0, react_1.useMemo)(() => propValues !== null && propValues !== void 0 ? propValues : [], [propValues]);
55
67
  const selectedOptionsRef = (0, react_1.useRef)(options.filter((o) => values.includes(getOptionValue(o))));
56
68
  const selectedOptions = (0, react_1.useMemo)(() => {
@@ -77,7 +89,9 @@ function ComboBoxBase(props) {
77
89
  });
78
90
  const { searchValue } = fieldState;
79
91
  const filteredOptions = (0, react_1.useMemo)(() => {
80
- return !searchValue ? options : options.filter((o) => contains(getOptionLabel(o), searchValue));
92
+ return !searchValue
93
+ ? options
94
+ : options.filter((o) => contains(getOptionLabel(o), searchValue) || o === exports.addNewOption);
81
95
  }, [options, searchValue, getOptionLabel, contains]);
82
96
  /** Resets field's input value and filtered options list for cases where the user exits the field without making changes (on Escape, or onBlur) */
83
97
  function resetField() {
@@ -99,6 +113,11 @@ function ComboBoxBase(props) {
99
113
  }
100
114
  const selectedKeys = [...keys.values()];
101
115
  const selectedOptions = options.filter((o) => selectedKeys.includes((0, Value_1.valueToKey)(getOptionValue(o))));
116
+ if (!multiselect && selectedOptions[0] === exports.addNewOption && onAddNew) {
117
+ onAddNew(fieldState.inputValue);
118
+ state.close();
119
+ return;
120
+ }
102
121
  selectionChanged && onSelect(selectedKeys.map(Value_1.keyToValue), selectedOptions);
103
122
  if (!multiselect) {
104
123
  // Close menu upon selection change only for Single selection mode
@@ -243,7 +262,7 @@ function getInputValue(selectedOptions, getOptionLabel, multiselect, nothingSele
243
262
  : "";
244
263
  }
245
264
  /** Transforms/simplifies `optionsOrLoad` into just options, with unsetLabel maybe added. */
246
- function initializeOptions(optionsOrLoad, getOptionValue, unsetLabel) {
265
+ function initializeOptions(optionsOrLoad, getOptionValue, unsetLabel, addNew) {
247
266
  const opts = [];
248
267
  if (unsetLabel) {
249
268
  opts.push(exports.unsetOption);
@@ -268,11 +287,15 @@ function initializeOptions(optionsOrLoad, getOptionValue, unsetLabel) {
268
287
  });
269
288
  }
270
289
  }
290
+ if (addNew) {
291
+ opts.push(exports.addNewOption);
292
+ }
271
293
  return opts;
272
294
  }
273
295
  exports.initializeOptions = initializeOptions;
274
296
  /** A marker option to automatically add an "Unset" option to the start of options. */
275
297
  exports.unsetOption = {};
298
+ exports.addNewOption = { id: "new", name: "Add New" };
276
299
  function disabledOptionToKeyedTuple(disabledOption) {
277
300
  if (typeof disabledOption === "object" && disabledOption !== null) {
278
301
  return [(0, Value_1.valueToKey)(disabledOption.value), disabledOption.reason];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homebound/beam",
3
- "version": "2.365.1",
3
+ "version": "2.366.1",
4
4
  "author": "Homebound",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",