@homebound/beam 2.375.0 → 2.377.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.
@@ -92,13 +92,15 @@ function GridTable(props) {
92
92
  const columnsWithIds = (0, react_1.useMemo)(() => (0, columns_1.assignDefaultColumnIds)(_columns), [_columns]);
93
93
  // We only use this in as=virtual mode, but keep this here for rowLookup to use
94
94
  const virtuosoRef = (0, react_1.useRef)(null);
95
+ // Stores the current rendered range of rows from virtuoso (used for determining if we can skip re-scrolling to a row if already in view)
96
+ const virtuosoRangeRef = (0, react_1.useRef)(null);
95
97
  // Use this ref to watch for changes in the GridTable's container and resize columns accordingly.
96
98
  const resizeRef = (0, react_1.useRef)(null);
97
99
  const api = (0, react_1.useMemo)(() => {
98
100
  var _a;
99
101
  // Let the user pass in their own api handle, otherwise make our own
100
102
  const api = (_a = props.api) !== null && _a !== void 0 ? _a : new GridTableApi_1.GridTableApiImpl();
101
- api.init(persistCollapse, virtuosoRef);
103
+ api.init(persistCollapse, virtuosoRef, virtuosoRangeRef);
102
104
  api.setActiveRowId(activeRowId);
103
105
  api.setActiveCellId(activeCellId);
104
106
  // Push the initial columns directly into tableState, b/c that is what
@@ -294,7 +296,7 @@ function GridTable(props) {
294
296
  // behave semantically the same as `as=div` did for its tests.
295
297
  const _as = as === "virtual" && runningInJest ? "div" : as;
296
298
  const rowStateContext = (0, react_1.useMemo)(() => ({ tableState: tableState }), [tableState]);
297
- return ((0, jsx_runtime_1.jsx)(TableState_1.TableStateContext.Provider, { value: rowStateContext, children: (0, jsx_runtime_1.jsxs)(PresentationContext_1.PresentationProvider, { fieldProps: fieldProps, wrap: (_d = style === null || style === void 0 ? void 0 : style.presentationSettings) === null || _d === void 0 ? void 0 : _d.wrap, children: [(0, jsx_runtime_1.jsx)("div", { ref: resizeRef, css: (0, components_1.getTableRefWidthStyles)(as === "virtual") }), renders[_as](style, id, columns, visibleDataRows, keptSelectedRows, firstRowMessage, stickyHeader, xss, virtuosoRef, tableHeadRows, stickyOffset, infiniteScroll)] }) }));
299
+ return ((0, jsx_runtime_1.jsx)(TableState_1.TableStateContext.Provider, { value: rowStateContext, children: (0, jsx_runtime_1.jsxs)(PresentationContext_1.PresentationProvider, { fieldProps: fieldProps, wrap: (_d = style === null || style === void 0 ? void 0 : style.presentationSettings) === null || _d === void 0 ? void 0 : _d.wrap, children: [(0, jsx_runtime_1.jsx)("div", { ref: resizeRef, css: (0, components_1.getTableRefWidthStyles)(as === "virtual") }), renders[_as](style, id, columns, visibleDataRows, keptSelectedRows, firstRowMessage, stickyHeader, xss, virtuosoRef, virtuosoRangeRef, tableHeadRows, stickyOffset, infiniteScroll)] }) }));
298
300
  }
299
301
  // Determine which HTML element to use to build the GridTable
300
302
  const renders = {
@@ -303,7 +305,7 @@ const renders = {
303
305
  virtual: renderVirtual,
304
306
  };
305
307
  /** Renders table using divs with flexbox rows, which is the default render */
306
- function renderDiv(style, id, columns, visibleDataRows, keptSelectedRows, firstRowMessage, stickyHeader, xss, _virtuosoRef, tableHeadRows, stickyOffset, _infiniteScroll) {
308
+ function renderDiv(style, id, columns, visibleDataRows, keptSelectedRows, firstRowMessage, stickyHeader, xss, _virtuosoRef, _virtuosoRangeRef, tableHeadRows, stickyOffset, _infiniteScroll) {
307
309
  return ((0, jsx_runtime_1.jsxs)("div", { css: {
308
310
  // Use `fit-content` to ensure the width of the table takes up the full width of its content.
309
311
  // Otherwise, the table's width would be that of its container, which may not be as wide as the table itself.
@@ -323,7 +325,7 @@ function renderDiv(style, id, columns, visibleDataRows, keptSelectedRows, firstR
323
325
  }, children: [keptSelectedRows, firstRowMessage && ((0, jsx_runtime_1.jsx)("div", { css: { ...style.firstRowMessageCss }, "data-gridrow": true, children: firstRowMessage })), visibleDataRows] })] }));
324
326
  }
325
327
  /** Renders as a table, primarily/solely for good print support. */
326
- function renderTable(style, id, columns, visibleDataRows, keptSelectedRows, firstRowMessage, stickyHeader, xss, _virtuosoRef, tableHeadRows, stickyOffset, _infiniteScroll) {
328
+ function renderTable(style, id, columns, visibleDataRows, keptSelectedRows, firstRowMessage, stickyHeader, xss, _virtuosoRef, _virtuosoRangeRef, tableHeadRows, stickyOffset, _infiniteScroll) {
327
329
  return ((0, jsx_runtime_1.jsxs)("table", { css: {
328
330
  ...Css_1.Css.w100.add("borderCollapse", "separate").add("borderSpacing", "0").$,
329
331
  ...Css_1.Css.addIn("& tr ", { pageBreakAfter: "auto", pageBreakInside: "avoid" }).$,
@@ -357,7 +359,7 @@ function renderTable(style, id, columns, visibleDataRows, keptSelectedRows, firs
357
359
  * [2]: https://github.com/tannerlinsley/react-virtual/issues/85
358
360
  * [3]: https://github.com/tannerlinsley/react-virtual/issues/108
359
361
  */
360
- function renderVirtual(style, id, columns, visibleDataRows, keptSelectedRows, firstRowMessage, stickyHeader, xss, virtuosoRef, tableHeadRows, _stickyOffset, infiniteScroll) {
362
+ function renderVirtual(style, id, columns, visibleDataRows, keptSelectedRows, firstRowMessage, stickyHeader, xss, virtuosoRef, virtuosoRangeRef, tableHeadRows, _stickyOffset, infiniteScroll) {
361
363
  var _a;
362
364
  // eslint-disable-next-line react-hooks/rules-of-hooks
363
365
  const { footerStyle, listStyle } = (0, react_1.useMemo)(() => {
@@ -402,6 +404,8 @@ function renderVirtual(style, id, columns, visibleDataRows, keptSelectedRows, fi
402
404
  }
403
405
  // Lastly render the table body rows
404
406
  return visibleDataRows[index];
407
+ }, rangeChanged: (newRange) => {
408
+ virtuosoRangeRef.current = newRange;
405
409
  }, totalCount: tableHeadRows.length + (firstRowMessage ? 1 : 0) + visibleDataRows.length + keptSelectedRows.length, ...(infiniteScroll
406
410
  ? {
407
411
  increaseViewportBy: {
@@ -1,5 +1,5 @@
1
1
  import { MutableRefObject } from "react";
2
- import { VirtuosoHandle } from "react-virtuoso";
2
+ import { ListRange, VirtuosoHandle } from "react-virtuoso";
3
3
  import { GridRowLookup, GridTableScrollOptions, MaybeFn } from "../index";
4
4
  import { GridDataRow } from "./components/Row";
5
5
  import { DiscriminateUnion, Kinded } from "./types";
@@ -77,11 +77,12 @@ export type GridRowApi<R extends Kinded> = GridTableApi<R> & {
77
77
  export declare class GridTableApiImpl<R extends Kinded> implements GridTableApi<R> {
78
78
  readonly tableState: TableState<R>;
79
79
  virtuosoRef: MutableRefObject<VirtuosoHandle | null>;
80
+ virtuosoRangeRef: MutableRefObject<ListRange | null>;
80
81
  lookup: GridRowLookup<R>;
81
82
  constructor();
82
83
  /** Called once by the GridTable when it takes ownership of this api instance. */
83
- init(persistCollapse: string | undefined, virtuosoRef: MutableRefObject<VirtuosoHandle | null>): void;
84
- scrollToIndex(index: GridTableScrollOptions): void;
84
+ init(persistCollapse: string | undefined, virtuosoRef: MutableRefObject<VirtuosoHandle | null>, virtuosoRangeRef: MutableRefObject<ListRange | null>): void;
85
+ scrollToIndex(indexOrOptions: GridTableScrollOptions): void;
85
86
  getSelectedRowIds(kind?: string): string[];
86
87
  private getSelectedRowIdsImpl;
87
88
  getSelectedRows(kind?: string): any;
@@ -33,6 +33,7 @@ class GridTableApiImpl {
33
33
  // This is public to GridTable but not exported outside of Beam
34
34
  this.tableState = new TableState_1.TableState(this);
35
35
  this.virtuosoRef = { current: null };
36
+ this.virtuosoRangeRef = { current: null };
36
37
  // This instance gets spread into each row's GridRowApi, so bind the methods up-front
37
38
  bindMethods(this);
38
39
  // Memoize these so that if the user is creating new `data` instances on every render, they
@@ -43,16 +44,21 @@ class GridTableApiImpl {
43
44
  this.getSelectedRowIdsImpl = (0, mobx_utils_1.computedFn)(this.getSelectedRowIdsImpl, { equals: mobx_1.comparer.shallow });
44
45
  }
45
46
  /** Called once by the GridTable when it takes ownership of this api instance. */
46
- init(persistCollapse, virtuosoRef) {
47
+ init(persistCollapse, virtuosoRef, virtuosoRangeRef) {
47
48
  // Technically this drives both row-collapse and column-expanded
48
49
  if (persistCollapse)
49
50
  this.tableState.loadCollapse(persistCollapse);
50
51
  this.virtuosoRef = virtuosoRef;
51
- this.lookup = (0, index_1.createRowLookup)(this, virtuosoRef);
52
- }
53
- scrollToIndex(index) {
54
- this.virtuosoRef.current &&
55
- this.virtuosoRef.current.scrollToIndex(typeof index === "number" ? { index, behavior: "smooth" } : index);
52
+ this.virtuosoRangeRef = virtuosoRangeRef;
53
+ this.lookup = (0, index_1.createRowLookup)(this, virtuosoRef, virtuosoRangeRef);
54
+ }
55
+ scrollToIndex(indexOrOptions) {
56
+ if (!this.virtuosoRef.current)
57
+ return;
58
+ const scrollToOpts = typeof indexOrOptions === "number" ? { index: indexOrOptions, behavior: "smooth" } : indexOrOptions;
59
+ if ((0, index_1.shouldSkipScrollTo)(scrollToOpts.index, this.virtuosoRangeRef))
60
+ return;
61
+ this.virtuosoRef.current.scrollToIndex(scrollToOpts);
56
62
  }
57
63
  getSelectedRowIds(kind) {
58
64
  return this.getSelectedRowIdsImpl(kind !== null && kind !== void 0 ? kind : undefined);
@@ -14,7 +14,7 @@ export { cardStyle, condensedStyle, defaultStyle, getTableStyles } from "./Table
14
14
  export type { GridStyle, RowStyle, RowStyles } from "./TableStyles";
15
15
  export * from "./types";
16
16
  export * from "./utils/columns";
17
- export { createRowLookup } from "./utils/GridRowLookup";
17
+ export { createRowLookup, shouldSkipScrollTo } from "./utils/GridRowLookup";
18
18
  export type { GridRowLookup } from "./utils/GridRowLookup";
19
19
  export { simpleDataRows, simpleHeader } from "./utils/simpleHelpers";
20
20
  export type { SimpleHeaderAndData } from "./utils/simpleHelpers";
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.setGridTableDefaults = exports.setDefaultStyle = exports.GridTable = exports.TableStateContext = exports.TableState = exports.simpleHeader = exports.simpleDataRows = exports.createRowLookup = exports.getTableStyles = exports.defaultStyle = exports.condensedStyle = exports.cardStyle = exports.useGridTableApi = exports.SortHeader = exports.Row = exports.EditColumnsButton = exports.rowLinkRenderFn = exports.rowClickRenderFn = exports.headerRenderFn = exports.defaultRenderFn = void 0;
17
+ exports.setGridTableDefaults = exports.setDefaultStyle = exports.GridTable = exports.TableStateContext = exports.TableState = exports.simpleHeader = exports.simpleDataRows = exports.shouldSkipScrollTo = exports.createRowLookup = exports.getTableStyles = exports.defaultStyle = exports.condensedStyle = exports.cardStyle = exports.useGridTableApi = exports.SortHeader = exports.Row = exports.EditColumnsButton = exports.rowLinkRenderFn = exports.rowClickRenderFn = exports.headerRenderFn = exports.defaultRenderFn = void 0;
18
18
  var cell_1 = require("./components/cell");
19
19
  Object.defineProperty(exports, "defaultRenderFn", { enumerable: true, get: function () { return cell_1.defaultRenderFn; } });
20
20
  Object.defineProperty(exports, "headerRenderFn", { enumerable: true, get: function () { return cell_1.headerRenderFn; } });
@@ -41,6 +41,7 @@ __exportStar(require("./types"), exports);
41
41
  __exportStar(require("./utils/columns"), exports);
42
42
  var GridRowLookup_1 = require("./utils/GridRowLookup");
43
43
  Object.defineProperty(exports, "createRowLookup", { enumerable: true, get: function () { return GridRowLookup_1.createRowLookup; } });
44
+ Object.defineProperty(exports, "shouldSkipScrollTo", { enumerable: true, get: function () { return GridRowLookup_1.shouldSkipScrollTo; } });
44
45
  var simpleHelpers_1 = require("./utils/simpleHelpers");
45
46
  Object.defineProperty(exports, "simpleDataRows", { enumerable: true, get: function () { return simpleHelpers_1.simpleDataRows; } });
46
47
  Object.defineProperty(exports, "simpleHeader", { enumerable: true, get: function () { return simpleHelpers_1.simpleHeader; } });
@@ -1,5 +1,5 @@
1
1
  import { MutableRefObject } from "react";
2
- import { VirtuosoHandle } from "react-virtuoso";
2
+ import { ListRange, VirtuosoHandle } from "react-virtuoso";
3
3
  import { GridDataRow } from "../components/Row";
4
4
  import { GridTableApiImpl } from "../GridTableApi";
5
5
  import { DiscriminateUnion, GridColumnWithId, Kinded } from "../types";
@@ -16,13 +16,18 @@ export interface GridRowLookup<R extends Kinded> {
16
16
  };
17
17
  /** Returns the list of currently filtered/sorted rows, without headers. */
18
18
  currentList(): readonly GridDataRow<R>[];
19
- /** Scroll's to the row with the given kind + id. Requires using `as=virtual`. */
19
+ /**
20
+ * Scroll's to the row with the given kind + id. Requires using `as=virtual`.
21
+ * Will skip re-scrolling to a row if it's already visible.
22
+ */
20
23
  scrollTo(kind: R["kind"], id: string): void;
21
24
  }
22
25
  interface NextPrev<R extends Kinded> {
23
26
  next: GridDataRow<R> | undefined;
24
27
  prev: GridDataRow<R> | undefined;
25
28
  }
26
- export declare function createRowLookup<R extends Kinded>(api: GridTableApiImpl<R>, virtuosoRef: MutableRefObject<VirtuosoHandle | null>): GridRowLookup<R>;
29
+ export declare function createRowLookup<R extends Kinded>(api: GridTableApiImpl<R>, virtuosoRef: MutableRefObject<VirtuosoHandle | null>, virtuosoRangeRef: MutableRefObject<ListRange | null>): GridRowLookup<R>;
27
30
  export declare function getKinds<R extends Kinded>(columns: GridColumnWithId<R>[]): R[];
31
+ /** Optionally takes into consideration if a row is already in view before attempting to scroll to it. */
32
+ export declare function shouldSkipScrollTo(index: number, virtuosoRangeRef: MutableRefObject<ListRange | null>): boolean;
28
33
  export {};
@@ -2,8 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createRowLookup = createRowLookup;
4
4
  exports.getKinds = getKinds;
5
+ exports.shouldSkipScrollTo = shouldSkipScrollTo;
5
6
  const types_1 = require("../types");
6
- function createRowLookup(api, virtuosoRef) {
7
+ function createRowLookup(api, virtuosoRef, virtuosoRangeRef) {
7
8
  return {
8
9
  scrollTo(kind, id) {
9
10
  if (virtuosoRef.current === null) {
@@ -12,6 +13,8 @@ function createRowLookup(api, virtuosoRef) {
12
13
  throw new Error("scrollTo is only supported for as=virtual");
13
14
  }
14
15
  const index = api.tableState.visibleRows.findIndex((r) => r && r.kind === kind && r.row.id === id);
16
+ if (shouldSkipScrollTo(index, virtuosoRangeRef))
17
+ return;
15
18
  virtuosoRef.current.scrollToIndex({ index, behavior: "smooth" });
16
19
  },
17
20
  currentList() {
@@ -53,3 +56,14 @@ function createRowLookup(api, virtuosoRef) {
53
56
  function getKinds(columns) {
54
57
  return Object.keys(columns.find((c) => !c.isAction) || {}).filter((key) => !types_1.nonKindGridColumnKeys.includes(key));
55
58
  }
59
+ /** Optionally takes into consideration if a row is already in view before attempting to scroll to it. */
60
+ function shouldSkipScrollTo(index, virtuosoRangeRef) {
61
+ if (!virtuosoRangeRef.current)
62
+ return false;
63
+ const isAlreadyInView =
64
+ // Add 1 on each end to account for "overscan" where the next out of view row is usually already rendered. This isn't a perfect solution,
65
+ // but our current "overscan" is only set to 50px, so it should be close enough and the library recommended alternative of adding an
66
+ // intersection observer to each row seems like a not worth it performance hit (https://github.com/petyosi/react-virtuoso/issues/118)
67
+ index >= virtuosoRangeRef.current.startIndex - 1 && index <= virtuosoRangeRef.current.endIndex + 1;
68
+ return isAlreadyInView;
69
+ }
@@ -1,14 +1,10 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.addNewOption = exports.unsetOption = void 0;
7
4
  exports.ComboBoxBase = ComboBoxBase;
8
5
  exports.initializeOptions = initializeOptions;
9
6
  exports.disabledOptionToKeyedTuple = disabledOptionToKeyedTuple;
10
7
  const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
11
- const fast_deep_equal_1 = __importDefault(require("fast-deep-equal"));
12
8
  const react_1 = require("react");
13
9
  const react_aria_1 = require("react-aria");
14
10
  const react_stately_1 = require("react-stately");
@@ -68,18 +64,7 @@ function ComboBoxBase(props) {
68
64
  : [propOptions.current, propOptions.options, unsetLabel, onAddNew]);
69
65
  const values = (0, react_1.useMemo)(() => propValues !== null && propValues !== void 0 ? propValues : [], [propValues]);
70
66
  const inputStylePalette = (0, react_1.useMemo)(() => propsInputStylePalette, [propsInputStylePalette]);
71
- const selectedOptionsRef = (0, react_1.useRef)(options.filter((o) => values.includes(getOptionValue(o))));
72
- const selectedOptions = (0, react_1.useMemo)(() => {
73
- // `selectedOptions` should only ever update if the `values` prop actually change.
74
- // Assuming `values` is a state variable, then it should hold its identity until it _really_ changes.
75
- // Though, it is possible that the `options` prop has changed, which is a dependency on this `useMemo`.
76
- // That could trigger an unnecessary new reference for `selectedOptions`, and cause additional renders or unexpected state changes.
77
- // We should avoid updating `selectedOptions` unless `values` has actually changed.
78
- if (!(0, fast_deep_equal_1.default)([...values].sort(), selectedOptionsRef.current.map(getOptionValue).sort())) {
79
- selectedOptionsRef.current = options.filter((o) => values.includes(getOptionValue(o)));
80
- }
81
- return selectedOptionsRef.current;
82
- }, [options, values, getOptionValue]);
67
+ const selectedOptions = (0, react_1.useMemo)(() => options.filter((o) => values.includes(getOptionValue(o))), [options, values, getOptionValue]);
83
68
  const { contains } = (0, react_aria_1.useFilter)({ sensitivity: "base" });
84
69
  const isDisabled = !!disabled;
85
70
  const isReadOnly = !!readOnly;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homebound/beam",
3
- "version": "2.375.0",
3
+ "version": "2.377.0",
4
4
  "author": "Homebound",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",