@homebound/beam 2.95.1 → 2.96.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.
package/README.md CHANGED
@@ -23,7 +23,7 @@ _To see the latest designs, check out the [Figma](https://www.figma.com/file/aWU
23
23
 
24
24
  ## Beam's API Design Approach
25
25
 
26
- Beam is specifically "Homebound's design system". Given this extremely narrow purpose, we can lean into the simplicity of:
26
+ Beam is specifically "Homebound's Design System". Given this extremely narrow purpose, we can lean into the simplicity of:
27
27
 
28
28
  - We don't need to support everything for everyone
29
29
  - We can prefer API/UX consistency & simplicity over configuration & complexity
@@ -209,7 +209,7 @@ declare type GridRowKind<R extends Kinded, P extends R["kind"]> = DiscriminateUn
209
209
  * - For server-side sorting, it's the sortKey to pass back to the server to
210
210
  * request "sort by this column".
211
211
  *
212
- * - For client-side sorting, it the type `number`, to represent the current
212
+ * - For client-side sorting, it's type `number`, to represent the current
213
213
  * column being sorted, in which case we use the GridCellContent.value.
214
214
  */
215
215
  export declare type GridColumn<R extends Kinded, S = {}> = {
@@ -270,7 +270,8 @@ export declare type GridCellAlignment = "left" | "right" | "center";
270
270
  * primitive value for filtering and sorting.
271
271
  */
272
272
  export declare type GridCellContent = {
273
- content: ReactNode;
273
+ /** The JSX content of the cell. Virtual tables that client-side sort should use a function to avaid perf overhead. */
274
+ content: ReactNode | (() => ReactNode);
274
275
  alignment?: GridCellAlignment;
275
276
  /** Allow value to be a function in case it's a dynamic value i.e. reading from an inline-edited proxy. */
276
277
  value?: MaybeFn<number | string | Date | boolean | null | undefined>;
@@ -120,7 +120,7 @@ function GridTable(props) {
120
120
  stickyOffset,
121
121
  openCards: nestedCards ? nestedCards.currentOpenCards() : undefined,
122
122
  ...sortProps,
123
- }), `${row.kind}-${row.id}`) }), void 0));
123
+ }), `${row.kind}-${row.id}`) }), `${row.kind}-${row.id}`));
124
124
  }
125
125
  // Split out the header rows from the data rows so that we can put an `infoMessage` in between them (if needed).
126
126
  const headerRows = [];
@@ -256,11 +256,14 @@ function renderTable(style, id, columns, headerRows, filteredRows, firstRowMessa
256
256
  * [3]: https://github.com/tannerlinsley/react-virtual/issues/108
257
257
  */
258
258
  function renderVirtual(style, id, columns, headerRows, filteredRows, firstRowMessage, stickyHeader, firstLastColumnWidth, xss, virtuosoRef) {
259
- var _a;
260
- const { paddingBottom, ...otherRootStyles } = (_a = style.rootCss) !== null && _a !== void 0 ? _a : {};
259
+ const { footerStyle, listStyle } = (0, react_1.useMemo)(() => {
260
+ var _a;
261
+ const { paddingBottom, ...otherRootStyles } = (_a = style.rootCss) !== null && _a !== void 0 ? _a : {};
262
+ return { footerStyle: { paddingBottom }, listStyle: { ...style, rootCss: otherRootStyles } };
263
+ }, [style]);
261
264
  return ((0, jsx_runtime_1.jsx)(react_virtuoso_1.Virtuoso, { ref: virtuosoRef, components: {
262
- List: VirtualRoot({ ...style, rootCss: otherRootStyles }, columns, id, firstLastColumnWidth, xss),
263
- Footer: () => (0, jsx_runtime_1.jsx)("div", { css: { paddingBottom } }, void 0),
265
+ List: VirtualRoot(listStyle, columns, id, firstLastColumnWidth, xss),
266
+ Footer: () => (0, jsx_runtime_1.jsx)("div", { css: footerStyle }, void 0),
264
267
  },
265
268
  // Pin/sticky both the header row(s) + firstRowMessage to the top
266
269
  topItemCount: (stickyHeader ? headerRows.length : 0) + (firstRowMessage ? 1 : 0), itemSize: (el) => {
@@ -297,7 +300,7 @@ function renderVirtual(style, id, columns, headerRows, filteredRows, firstRowMes
297
300
  * (solely to capture as params that we can't pass through react-virtuoso's API as props).
298
301
  */
299
302
  const VirtualRoot = (0, memoize_one_1.default)((gs, columns, id, firstLastColumnWidth, xss) => {
300
- return react_1.default.forwardRef(({ style, children }, ref) => {
303
+ return react_1.default.forwardRef(function VirtualRoot({ style, children }, ref) {
301
304
  // This re-renders each time we have new children in the view port
302
305
  return ((0, jsx_runtime_1.jsx)("div", Object.assign({ ref: ref, style: style, css: {
303
306
  ...Css_1.Css.dg.gtc(calcVirtualGridColumns(columns, firstLastColumnWidth)).$,
@@ -400,7 +403,7 @@ function maybeAddCardColumns(sizes, firstLastColumnWidth) {
400
403
  }
401
404
  function getIndentationCss(style, rowStyle, columnIndex, maybeContent) {
402
405
  // Look for cell-specific indent or row-specific indent (row-specific is only one the first column)
403
- const indent = (isContentAndSettings(maybeContent) && maybeContent.indent) || (columnIndex === 0 && (rowStyle === null || rowStyle === void 0 ? void 0 : rowStyle.indent));
406
+ const indent = (isGridCellContent(maybeContent) && maybeContent.indent) || (columnIndex === 0 && (rowStyle === null || rowStyle === void 0 ? void 0 : rowStyle.indent));
404
407
  return indent === 1 ? style.indentOneCss || {} : indent === 2 ? style.indentTwoCss || {} : {};
405
408
  }
406
409
  function getFirstOrLastCellCss(style, columnIndex, columns) {
@@ -448,10 +451,10 @@ function GridRow(props) {
448
451
  return;
449
452
  }
450
453
  const maybeContent = applyRowFn(column, row);
451
- currentColspan = isContentAndSettings(maybeContent) ? (_a = maybeContent.colspan) !== null && _a !== void 0 ? _a : 1 : 1;
454
+ currentColspan = isGridCellContent(maybeContent) ? (_a = maybeContent.colspan) !== null && _a !== void 0 ? _a : 1 : 1;
452
455
  const canSortColumn = ((sorting === null || sorting === void 0 ? void 0 : sorting.on) === "client" && column.clientSideSort !== false) ||
453
456
  ((sorting === null || sorting === void 0 ? void 0 : sorting.on) === "server" && !!column.serverSideSortKey);
454
- const content = toContent(maybeContent, isHeader, canSortColumn, style);
457
+ const content = toContent(maybeContent, isHeader, canSortColumn, (sorting === null || sorting === void 0 ? void 0 : sorting.on) === "client", style, as);
455
458
  (0, sortRows_1.ensureClientSideSortValueIsSortable)(sorting, isHeader, column, columnIndex, maybeContent);
456
459
  // Note that it seems expensive to calc a per-cell class name/CSS-in-JS output,
457
460
  // vs. setting global/table-wide CSS like `style.cellCss` on the root grid div with
@@ -499,26 +502,44 @@ const ObservedGridRow = react_1.default.memo((props) => ((0, jsx_runtime_1.jsx)(
499
502
  // Invoke this just as a regular function so that Observer sees the proxy access's
500
503
  return GridRow(props);
501
504
  } }, void 0)));
505
+ /** A heuristic to detect the result of `React.createElement` / i.e. JSX. */
506
+ function isJSX(content) {
507
+ return typeof content === "object" && content && "type" in content && "props" in content;
508
+ }
502
509
  /** If a column def return just string text for a given row, apply some default styling. */
503
- function toContent(content, isHeader, canSortColumn, style) {
504
- if (typeof content === "string" && isHeader && canSortColumn) {
510
+ function toContent(content, isHeader, canSortColumn, isClientSideSorting, style, as) {
511
+ content = isGridCellContent(content) ? content.content : content;
512
+ if (typeof content === "function") {
513
+ // Actually create the JSX by calling `content()` here (which should be as late as
514
+ // possible, i.e. only for visible rows if we're in a virtual table).
515
+ content = content();
516
+ }
517
+ else if (as === "virtual" && canSortColumn && isClientSideSorting && isJSX(content)) {
518
+ // When using client-side sorting, we call `applyRowFn` not only during rendering, but
519
+ // up-front against all rows (for the currently sorted column) to determine their
520
+ // sort values.
521
+ //
522
+ // Pedantically this means that any table using client-side sorting should not
523
+ // build JSX directly in its GridColumn functions, but this overhead is especially
524
+ // noticeable for large/virtualized tables, so we only enforce using functions
525
+ // for those tables.
526
+ throw new Error("GridTables with as=virtual & sortable columns should use functions that return JSX, instead of JSX");
527
+ }
528
+ if (content && typeof content === "string" && isHeader && canSortColumn) {
505
529
  return (0, jsx_runtime_1.jsx)(SortHeader_1.SortHeader, { content: content }, void 0);
506
530
  }
507
531
  else if (style.emptyCell && isContentEmpty(content)) {
508
532
  // If the content is empty and the user specified an `emptyCell` node, return that.
509
533
  return style.emptyCell;
510
534
  }
511
- else if (isContentAndSettings(content)) {
512
- return content.content;
513
- }
514
535
  return content;
515
536
  }
516
- function isContentAndSettings(content) {
537
+ function isGridCellContent(content) {
517
538
  return typeof content === "object" && !!content && "content" in content;
518
539
  }
519
540
  const emptyValues = ["", null, undefined];
520
541
  function isContentEmpty(content) {
521
- return emptyValues.includes(isContentAndSettings(content) ? content.content : content);
542
+ return emptyValues.includes(content);
522
543
  }
523
544
  /** Return the content for a given column def applied to a given row. */
524
545
  function applyRowFn(column, row) {
@@ -545,8 +566,8 @@ const defaultRenderFn = (as) => (key, css, content) => {
545
566
  };
546
567
  exports.GridCollapseContext = react_1.default.createContext({
547
568
  headerCollapsed: false,
548
- isCollapsed: (id) => false,
549
- toggleCollapsed: (id) => { },
569
+ isCollapsed: () => false,
570
+ toggleCollapsed: () => { },
550
571
  });
551
572
  /** Sets up the `GridContext` so that header cells can access the current sort settings. */
552
573
  const headerRenderFn = (columns, column, sortState, setSortKey, as) => (key, css, content) => {
@@ -585,7 +606,7 @@ const alignmentToTextAlign = {
585
606
  };
586
607
  // For alignment, use: 1) cell def, else 2) column def, else 3) left.
587
608
  function getJustification(column, maybeContent, as) {
588
- const alignment = (isContentAndSettings(maybeContent) && maybeContent.alignment) || column.align || "left";
609
+ const alignment = (isGridCellContent(maybeContent) && maybeContent.alignment) || column.align || "left";
589
610
  // Always apply text alignment.
590
611
  const textAlign = Css_1.Css.add("textAlign", alignmentToTextAlign[alignment]).$;
591
612
  if (as === "table") {
@@ -42,7 +42,7 @@ function ChipSelectField(props) {
42
42
  },
43
43
  onBlur: (e) => {
44
44
  // Do not call onBlur if focus moved to within the Popover
45
- if (popoverRef.current && popoverRef.current.contains(e.relatedTarget)) {
45
+ if ((popoverRef.current && popoverRef.current.contains(e.relatedTarget)) || showInput) {
46
46
  return;
47
47
  }
48
48
  (0, utils_1.maybeCall)(onBlur);
@@ -7,7 +7,7 @@ const utils_1 = require("../utils");
7
7
  function SelectField(props) {
8
8
  const { getOptionValue = (o) => o.id, // if unset, assume O implements HasId
9
9
  getOptionLabel = (o) => o.name, // if unset, assume O implements HasName
10
- value, options, onSelect, readOnly = false, errorMsg, onBlur, onFocus, disabled, } = props;
10
+ value, options, onSelect, readOnly = false, errorMsg, onBlur, onFocus, disabled, disabledOptions = [], } = props;
11
11
  const tid = (0, utils_1.useTestIds)(props, "select");
12
12
  const currentOption = options.find((o) => getOptionValue(o) === value) || options[0];
13
13
  return ((0, jsx_runtime_1.jsxs)("select", Object.assign({}, tid, { value:
@@ -24,7 +24,7 @@ function SelectField(props) {
24
24
  },
25
25
  // Read Only does not apply to `select` fields, instead we'll add in disabled for tests to verify.
26
26
  disabled: !!(disabled || readOnly), "data-error": !!errorMsg, "data-errormsg": errorMsg, "data-readonly": readOnly }, { children: [(0, jsx_runtime_1.jsx)("option", { disabled: true, value: "" }, void 0), options.map((option, i) => {
27
- return ((0, jsx_runtime_1.jsx)("option", Object.assign({ value: `${getOptionValue(option)}` }, { children: getOptionLabel(option) }), i));
27
+ return ((0, jsx_runtime_1.jsx)("option", Object.assign({ value: `${getOptionValue(option)}`, disabled: disabledOptions.includes(getOptionValue(option).toString()) }, { children: getOptionLabel(option) }), i));
28
28
  })] }), void 0));
29
29
  }
30
30
  exports.SelectField = SelectField;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homebound/beam",
3
- "version": "2.95.1",
3
+ "version": "2.96.1",
4
4
  "author": "Homebound",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",