@homebound/beam 2.99.1 → 2.101.2

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.
@@ -9,7 +9,7 @@ export interface IconProps extends AriaAttributes, DOMProps {
9
9
  /** The size of the icon in increments, i.e. 1 == 8px, default is 3 == 24px. */
10
10
  inc?: number;
11
11
  /** Styles overrides */
12
- xss?: Xss<Margin>;
12
+ xss?: Xss<Margin | "visibility">;
13
13
  }
14
14
  export declare const Icon: React.MemoExoticComponent<(props: IconProps) => import("@emotion/react/jsx-runtime").JSX.Element>;
15
15
  /**
@@ -103,13 +103,13 @@ export declare type RowTuple<R extends Kinded> = [GridDataRow<R> | undefined, Re
103
103
  export declare type GridSortConfig<S> = {
104
104
  on: "client";
105
105
  /** The optional initial column (index in columns) and direction to sort. */
106
- initial?: [S | GridColumn<any>, Direction];
106
+ initial?: [S | GridColumn<any>, Direction] | undefined;
107
107
  } | {
108
108
  on: "server";
109
109
  /** The current sort by value + direction (if server-side sorting). */
110
110
  value?: [S, Direction];
111
- /** Callback for when the column is sorted (if server-side sorting). */
112
- onSort: (orderBy: S, direction: Direction) => void;
111
+ /** Callback for when the column is sorted (if server-side sorting). Parameters set to `undefined` is a signal to return to the initial sort state */
112
+ onSort: (orderBy: S | undefined, direction: Direction | undefined) => void;
113
113
  };
114
114
  export interface GridTableProps<R extends Kinded, S, X> {
115
115
  id?: string;
@@ -185,18 +185,10 @@ export declare type GridTableApi = {
185
185
  */
186
186
  export declare function GridTable<R extends Kinded, S = {}, X extends Only<GridTableXss, X> = {}>(props: GridTableProps<R, S, X>): import("@emotion/react/jsx-runtime").JSX.Element;
187
187
  /**
188
- * Creates a `grid-template-column` specific to our virtual output.
189
- *
190
- * Because of two things:
191
- *
192
- * a) react-virtuoso puts the header in a different div than the normal rows, and
193
- *
194
- * b) content-aware sizing just in general look janky/constantly resize while scrolling
195
- *
196
- * When we're as=virtual, we change our default + enforce only fixed-sized units (% and px)
188
+ * Calculates column widths using a flexible `calc()` definition that allows for consistent column alignment without the use of `<table />`, CSS Grid, etc layouts.
189
+ * Enforces only fixed-sized units (% and px)
197
190
  */
198
- export declare function calcVirtualGridColumns(columns: GridColumn<any>[], firstLastColumnWidth: number | undefined): string[];
199
- export declare function calcDivGridColumns(columns: GridColumn<any>[], firstLastColumnWidth: number | undefined): string;
191
+ export declare function calcColumnSizes(columns: GridColumn<any>[], firstLastColumnWidth: number | undefined): string[];
200
192
  /**
201
193
  * Given an ADT of type T, performs a look up and returns the type of kind K.
202
194
  *
@@ -225,21 +217,8 @@ export declare type GridColumn<R extends Kinded, S = {}> = {
225
217
  } ? (data: D, row: GridRowKind<R, K>) => ReactNode | GridCellContent : (row: GridRowKind<R, K>) => ReactNode | GridCellContent);
226
218
  } & {
227
219
  /**
228
- * The column's grid column width.
229
- *
230
- * For `as=div` output:
231
- *
232
- * - Any CSS grid units are supported
233
- * - Numbers are treated as `fr` units
234
- * - The default value is `auto`, which in CSS grid will do content-aware/responsive layout.
235
- *
236
- * For `as=virtual` output:
237
- *
238
- * - Only px, percentage, or fr units are supported, due to a) react-virtuoso puts the sticky header
239
- * rows in a separate `div` and so we end up with two `grid-template-columns`, so cannot rely on
240
- * any content-aware sizing, and b) content-aware sizing in a scrolling/virtual table results in
241
- * a ~janky experience as the columns will constantly resize as new/different content is put in/out
242
- * of the DOM.
220
+ * The column's width.
221
+ * - Only px, percentage, or fr units are supported, due to each GridRow acting as an independent table. This ensures consistent alignment between rows.
243
222
  * - Numbers are treated as `fr` units
244
223
  * - The default value is `1fr`
245
224
  */
@@ -277,7 +256,7 @@ export declare type GridCellAlignment = "left" | "right" | "center";
277
256
  * primitive value for filtering and sorting.
278
257
  */
279
258
  export declare type GridCellContent = {
280
- /** The JSX content of the cell. Virtual tables that client-side sort should use a function to avaid perf overhead. */
259
+ /** The JSX content of the cell. Virtual tables that client-side sort should use a function to avoid perf overhead. */
281
260
  content: ReactNode | (() => ReactNode);
282
261
  alignment?: GridCellAlignment;
283
262
  /** Allow value to be a function in case it's a dynamic value i.e. reading from an inline-edited proxy. */
@@ -22,7 +22,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
22
22
  return (mod && mod.__esModule) ? mod : { "default": mod };
23
23
  };
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.matchesFilter = exports.GridCollapseContext = exports.applyRowFn = exports.calcDivGridColumns = exports.calcVirtualGridColumns = exports.GridTable = exports.setGridTableDefaults = exports.setDefaultStyle = exports.setRunningInJest = exports.emptyCell = exports.DESC = exports.ASC = void 0;
25
+ exports.matchesFilter = exports.GridCollapseContext = exports.applyRowFn = exports.calcColumnSizes = exports.GridTable = exports.setGridTableDefaults = exports.setDefaultStyle = exports.setRunningInJest = exports.emptyCell = exports.DESC = exports.ASC = void 0;
26
26
  const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
27
27
  const memoize_one_1 = __importDefault(require("memoize-one"));
28
28
  const mobx_react_1 = require("mobx-react");
@@ -92,12 +92,10 @@ function GridTable(props) {
92
92
  };
93
93
  }
94
94
  const [sortState, setSortKey] = (0, useSortState_1.useSortState)(columns, sorting);
95
- // Disclaimer that technically even though this is a useMemo, sortRows is mutating `rows` directly
96
95
  const maybeSorted = (0, react_1.useMemo)(() => {
97
96
  if ((sorting === null || sorting === void 0 ? void 0 : sorting.on) === "client" && sortState) {
98
97
  // If using client-side sort, the sortState use S = number
99
- (0, sortRows_1.sortRows)(columns, rows, sortState);
100
- return rows;
98
+ return (0, sortRows_1.sortRows)(columns, rows, sortState);
101
99
  }
102
100
  return rows;
103
101
  }, [columns, rows, sorting, sortState]);
@@ -106,7 +104,7 @@ function GridTable(props) {
106
104
  var _a;
107
105
  // Break up "foo bar" into `[foo, bar]` and a row must match both `foo` and `bar`
108
106
  const filters = (filter && filter.split(/ +/)) || [];
109
- const columnSizes = as === "virtual" ? calcVirtualGridColumns(columns, (_a = style.nestedCards) === null || _a === void 0 ? void 0 : _a.firstLastColumnWidth) : undefined;
107
+ const columnSizes = calcColumnSizes(columns, (_a = style.nestedCards) === null || _a === void 0 ? void 0 : _a.firstLastColumnWidth);
110
108
  function makeRowComponent(row) {
111
109
  // We only pass sortState to header rows, b/c non-headers rows shouldn't have to re-render on sorting
112
110
  // changes, and so by not passing the sortProps, it means the data rows' React.memo will still cache them.
@@ -219,11 +217,11 @@ exports.GridTable = GridTable;
219
217
  // Determine which HTML element to use to build the GridTable
220
218
  const renders = {
221
219
  table: renderTable,
222
- div: renderCssGrid,
220
+ div: renderDiv,
223
221
  virtual: renderVirtual,
224
222
  };
225
- /** Renders as a CSS Grid, which is the default / most well-supported rendered. */
226
- function renderCssGrid(style, id, columns, headerRows, filteredRows, firstRowMessage, _stickyHeader, firstLastColumnWidth, xss, _virtuosoRef) {
223
+ /** Renders table using divs with flexbox rows, which is the default render */
224
+ function renderDiv(style, id, columns, headerRows, filteredRows, firstRowMessage, _stickyHeader, firstLastColumnWidth, xss, _virtuosoRef) {
227
225
  var _a;
228
226
  // We must determine if the header is using nested card styles to account for
229
227
  // the opening and closing Chrome rows.
@@ -249,7 +247,8 @@ function renderCssGrid(style, id, columns, headerRows, filteredRows, firstRowMes
249
247
  */
250
248
  const firstNonHeaderRowIndex = (!isNestedCardStyleHeader ? 1 : 3) + 1;
251
249
  return ((0, jsx_runtime_1.jsxs)("div", Object.assign({ css: {
252
- ...Css_1.Css.dg.gtc(calcDivGridColumns(columns, firstLastColumnWidth)).$,
250
+ // Ensure all rows extend the same width
251
+ ...Css_1.Css.mw("fit-content").$,
253
252
  /*
254
253
  Using n + (firstNonHeaderRowIndex + 1) here to target all rows that
255
254
  are after the first non-header row. Since n starts at 0, we can use
@@ -265,7 +264,7 @@ function renderCssGrid(style, id, columns, headerRows, filteredRows, firstRowMes
265
264
  : {}),
266
265
  ...style.rootCss,
267
266
  ...xss,
268
- }, "data-testid": id }, { children: [headerRows.map(([, node]) => node), firstRowMessage && ((0, jsx_runtime_1.jsx)("div", Object.assign({ css: Css_1.Css.add("gridColumn", `${columns.length} span`).$ }, { children: (0, jsx_runtime_1.jsx)("div", Object.assign({ css: { ...style.firstRowMessageCss } }, { children: firstRowMessage }), void 0) }), void 0)), filteredRows.map(([, node]) => node)] }), void 0));
267
+ }, "data-testid": id }, { children: [headerRows.map(([, node]) => node), firstRowMessage && (0, jsx_runtime_1.jsx)("div", Object.assign({ css: { ...style.firstRowMessageCss } }, { children: firstRowMessage }), void 0), filteredRows.map(([, node]) => node)] }), void 0));
269
268
  }
270
269
  /** Renders as a table, primarily/solely for good print support. */
271
270
  function renderTable(style, id, columns, headerRows, filteredRows, firstRowMessage, _stickyHeader, _firstLastColumnWidth, xss, _virtuosoRef) {
@@ -305,7 +304,9 @@ function renderVirtual(style, id, columns, headerRows, filteredRows, firstRowMes
305
304
  const { paddingBottom, ...otherRootStyles } = (_a = style.rootCss) !== null && _a !== void 0 ? _a : {};
306
305
  return { footerStyle: { paddingBottom }, listStyle: { ...style, rootCss: otherRootStyles } };
307
306
  }, [style]);
308
- return ((0, jsx_runtime_1.jsx)(react_virtuoso_1.Virtuoso, { overscan: 5, ref: virtuosoRef, components: {
307
+ return ((0, jsx_runtime_1.jsx)(react_virtuoso_1.Virtuoso, { overscan: 5, ref: virtuosoRef,
308
+ // Add `minWidth: fit-content` to ensure a sticky header and the virtualized table body maintain same width
309
+ style: { minWidth: "fit-content" }, components: {
309
310
  List: VirtualRoot(listStyle, columns, id, firstLastColumnWidth, xss),
310
311
  Footer: () => (0, jsx_runtime_1.jsx)("div", { css: footerStyle }, void 0),
311
312
  },
@@ -348,7 +349,7 @@ function renderVirtual(style, id, columns, headerRows, filteredRows, firstRowMes
348
349
  * rows and the second represents the non-header rows (list rows).
349
350
  *
350
351
  * The main goal of this custom component is to:
351
- * - Customize the list wrapper to our css grid logic styles
352
+ * - Customize the list wrapper to our styles
352
353
  *
353
354
  * We wrap this in memoizeOne so that React.createElement sees a
354
355
  * consistent/stable component identity, even though technically we have a
@@ -361,7 +362,7 @@ const VirtualRoot = (0, memoize_one_1.default)((gs, _columns, id, _firstLastColu
361
362
  // table list generally has styles to scroll the page for windowing.
362
363
  const isHeader = Object.keys(style || {}).length === 0;
363
364
  // This re-renders each time we have new children in the viewport
364
- return ((0, jsx_runtime_1.jsx)("div", Object.assign({ ref: ref, style: { ...style, ...(gs.nestedCards ? { minWidth: "fit-content" } : {}) }, css: {
365
+ return ((0, jsx_runtime_1.jsx)("div", Object.assign({ ref: ref, style: { ...style, ...{ minWidth: "fit-content" } }, css: {
365
366
  // Add an extra `> div` due to Item + itemContent both having divs
366
367
  ...Css_1.Css.addIn("& > div + div > div > *", gs.betweenRowsCss || {}).$,
367
368
  // Table list styles only
@@ -376,17 +377,10 @@ const VirtualRoot = (0, memoize_one_1.default)((gs, _columns, id, _firstLastColu
376
377
  });
377
378
  });
378
379
  /**
379
- * Creates a `grid-template-column` specific to our virtual output.
380
- *
381
- * Because of two things:
382
- *
383
- * a) react-virtuoso puts the header in a different div than the normal rows, and
384
- *
385
- * b) content-aware sizing just in general look janky/constantly resize while scrolling
386
- *
387
- * When we're as=virtual, we change our default + enforce only fixed-sized units (% and px)
380
+ * Calculates column widths using a flexible `calc()` definition that allows for consistent column alignment without the use of `<table />`, CSS Grid, etc layouts.
381
+ * Enforces only fixed-sized units (% and px)
388
382
  */
389
- function calcVirtualGridColumns(columns, firstLastColumnWidth) {
383
+ function calcColumnSizes(columns, firstLastColumnWidth) {
390
384
  // For both default columns (1fr) as well as `w: 4fr` columns, we translate the width into an expression that looks like:
391
385
  // calc((100% - allOtherPercent - allOtherPx) * ((myFr / totalFr))`
392
386
  //
@@ -412,7 +406,7 @@ function calcVirtualGridColumns(columns, firstLastColumnWidth) {
412
406
  return { ...acc, claimedPercentages: acc.claimedPercentages + Number(w.replace("%", "")) };
413
407
  }
414
408
  else {
415
- throw new Error("as=virtual only supports px, percentage, or fr units");
409
+ throw new Error("Beam Table column width definition only supports px, percentage, or fr units");
416
410
  }
417
411
  }, { claimedPercentages: 0, claimedPixels: firstLastColumnWidth ? firstLastColumnWidth * 2 : 0, totalFr: 0 });
418
412
  // This is our "fake but for some reason it lines up better" fr calc
@@ -431,7 +425,7 @@ function calcVirtualGridColumns(columns, firstLastColumnWidth) {
431
425
  return fr(Number(w.replace("fr", "")));
432
426
  }
433
427
  else {
434
- throw new Error("as=virtual only supports px, percentage, or fr units");
428
+ throw new Error("Beam Table column width definition only supports px, percentage, or fr units");
435
429
  }
436
430
  }
437
431
  else {
@@ -440,25 +434,7 @@ function calcVirtualGridColumns(columns, firstLastColumnWidth) {
440
434
  });
441
435
  return maybeAddCardColumns(sizes, firstLastColumnWidth);
442
436
  }
443
- exports.calcVirtualGridColumns = calcVirtualGridColumns;
444
- function calcDivGridColumns(columns, firstLastColumnWidth) {
445
- const sizes = columns.map(({ w }) => {
446
- if (typeof w === "undefined") {
447
- // Hrm, I waffle between 'auto' or '1fr' being the better default here...
448
- return "auto";
449
- }
450
- else if (typeof w === "string") {
451
- // Use whatever the user passed in
452
- return w;
453
- }
454
- else {
455
- // Otherwise assume fr units
456
- return `${w}fr`;
457
- }
458
- });
459
- return maybeAddCardColumns(sizes, firstLastColumnWidth).join(" ");
460
- }
461
- exports.calcDivGridColumns = calcDivGridColumns;
437
+ exports.calcColumnSizes = calcColumnSizes;
462
438
  // If we're doing nested cards, we add extra 1st/last cells...
463
439
  function maybeAddCardColumns(sizes, firstLastColumnWidth) {
464
440
  return !firstLastColumnWidth ? sizes : [`${firstLastColumnWidth}px`, ...sizes, `${firstLastColumnWidth}px`];
@@ -482,19 +458,15 @@ function GridRow(props) {
482
458
  const rowStyle = rowStyles === null || rowStyles === void 0 ? void 0 : rowStyles[row.kind];
483
459
  const rowStyleCellCss = maybeApplyFunction(row, rowStyle === null || rowStyle === void 0 ? void 0 : rowStyle.cellCss);
484
460
  const rowCss = {
485
- // We add a display-contents so that we can have "row-level" div elements in our
486
- // DOM structure. I.e. grid is normally root-element > cell-elements, but we want
487
- // root-element > row-element > cell-elements, so that we can have a hook for
488
- // hovers and styling. In theory this would change with subgrids.
489
- // Only enable when using div as elements
490
461
  // For virtual tables use `display: flex` to keep all cells on the same row, but use `flexNone` to ensure the cells stay their defined widths
491
- ...(as === "table" ? {} : as === "virtual" ? Css_1.Css.df.addIn("&>*", Css_1.Css.flexNone.$).$ : Css_1.Css.display("contents").$),
462
+ ...(as === "table" ? {} : Css_1.Css.df.addIn("&>*", Css_1.Css.flexNone.$).$),
492
463
  ...(((rowStyle === null || rowStyle === void 0 ? void 0 : rowStyle.rowLink) || (rowStyle === null || rowStyle === void 0 ? void 0 : rowStyle.onClick)) &&
493
464
  style.rowHoverColor && {
494
465
  // Even though backgroundColor is set on the cellCss (due to display: content), the hover target is the row.
495
466
  "&:hover > *": Css_1.Css.cursorPointer.bgColor(maybeDarken(rowStyleCellCss === null || rowStyleCellCss === void 0 ? void 0 : rowStyleCellCss.backgroundColor, style.rowHoverColor)).$,
496
467
  }),
497
468
  ...maybeApplyFunction(row, rowStyle === null || rowStyle === void 0 ? void 0 : rowStyle.rowCss),
469
+ ...(isHeader && stickyHeader ? Css_1.Css.sticky.top(stickyOffset).z1.$ : undefined),
498
470
  };
499
471
  const Row = as === "table" ? "tr" : "div";
500
472
  const openCardStyles = typeof openCards === "string"
@@ -545,18 +517,11 @@ function GridRow(props) {
545
517
  ...getIndentationCss(style, rowStyle, columnIndex, maybeContent),
546
518
  // Then apply any header-specific override
547
519
  ...(isHeader && style.headerCellCss),
548
- // Non-virtualized tables use h100 so that all cells are the same height across the row.
549
- // Virtualized table rows use `display: flex;`, so the flex children are set to `align-self: stretch` by default, which achieves the same goal.
550
- // Though, we need to omit setting `h100` on the flex children, as a flex container needs a defined height set for `h100` to work on flex children
551
- ...(isHeader && as !== "virtual" ? Css_1.Css.h100.$ : undefined),
552
- ...maybeStickyHeaderStyles,
553
520
  // If we're within a card, use its background color
554
521
  ...(currentCard && Css_1.Css.bgColor(currentCard.bgColor).$),
555
- // Add in colspan css if needed
556
- ...(currentColspan > 1 ? Css_1.Css.gc(`span ${currentColspan}`).$ : {}),
557
522
  // And finally the specific cell's css (if any from GridCellContent)
558
523
  ...rowStyleCellCss,
559
- // For virtual tables we define the width of the column on each cell. Supports col spans.
524
+ // Define the width of the column on each cell. Supports col spans.
560
525
  ...(columnSizes && {
561
526
  width: `calc(${columnSizes
562
527
  .slice(maybeNestedCardColumnIndex, maybeNestedCardColumnIndex + currentColspan)
@@ -643,8 +608,8 @@ function applyRowFn(column, row) {
643
608
  exports.applyRowFn = applyRowFn;
644
609
  /** Renders our default cell element, i.e. if no row links and no custom renderCell are used. */
645
610
  const defaultRenderFn = (as) => (key, css, content) => {
646
- const Row = as === "table" ? "td" : "div";
647
- return ((0, jsx_runtime_1.jsx)(Row, Object.assign({ css: { ...css, ...tableRowStyles(as) } }, { children: content }), key));
611
+ const Cell = as === "table" ? "td" : "div";
612
+ return ((0, jsx_runtime_1.jsx)(Cell, Object.assign({ css: { ...css, ...tableRowStyles(as) } }, { children: content }), key));
648
613
  };
649
614
  exports.GridCollapseContext = react_1.default.createContext({
650
615
  headerCollapsed: false,
@@ -6,6 +6,7 @@ const react_1 = require("react");
6
6
  const Icon_1 = require("../Icon");
7
7
  const GridSortContext_1 = require("./GridSortContext");
8
8
  const Css_1 = require("../../Css");
9
+ const hooks_1 = require("../../hooks");
9
10
  const useTestIds_1 = require("../../utils/useTestIds");
10
11
  /**
11
12
  * Wraps column header names with up/down sorting icons.
@@ -20,8 +21,12 @@ const useTestIds_1 = require("../../utils/useTestIds");
20
21
  */
21
22
  function SortHeader(props) {
22
23
  const { content, xss } = props;
24
+ const { isHovered, hoverProps } = (0, hooks_1.useHover)({});
23
25
  const { sorted, toggleSort } = (0, react_1.useContext)(GridSortContext_1.GridSortContext);
24
26
  const tid = (0, useTestIds_1.useTestIds)(props, "sortHeader");
25
- return ((0, jsx_runtime_1.jsxs)("div", Object.assign({}, tid, { css: { ...Css_1.Css.df.aic.cursorPointer.selectNone.$, ...xss }, onClick: toggleSort }, { children: [content, sorted === "ASC" && (0, jsx_runtime_1.jsx)(Icon_1.Icon, Object.assign({ icon: "sortUp", inc: 2 }, tid.icon, { xss: Css_1.Css.mlPx(4).$ }), void 0), sorted === "DESC" && (0, jsx_runtime_1.jsx)(Icon_1.Icon, Object.assign({ icon: "sortDown", inc: 2 }, tid.icon, { xss: Css_1.Css.mlPx(4).$ }), void 0)] }), void 0));
27
+ return ((0, jsx_runtime_1.jsxs)("div", Object.assign({}, tid, { css: { ...Css_1.Css.df.aic.h100.cursorPointer.selectNone.$, ...xss } }, hoverProps, { onClick: toggleSort }, { children: [content, (0, jsx_runtime_1.jsx)("span", Object.assign({ css: Css_1.Css.fs0.$ }, { children: (0, jsx_runtime_1.jsx)(Icon_1.Icon, Object.assign({ icon: sorted === "DESC" ? "sortDown" : "sortUp", color: sorted !== undefined ? Css_1.Palette.LightBlue700 : Css_1.Palette.Gray400, xss: Css_1.Css.ml1
28
+ .visibility("hidden")
29
+ .if(isHovered || sorted !== undefined)
30
+ .visibility("visible").$, inc: 2 }, tid.icon), void 0) }), void 0)] }), void 0));
26
31
  }
27
32
  exports.SortHeader = SortHeader;
@@ -1,5 +1,5 @@
1
1
  import { ReactNode } from "react";
2
2
  import { GridCellContent, GridColumn, GridDataRow, GridSortConfig, Kinded } from "./GridTable";
3
3
  import { SortState } from "./useSortState";
4
- export declare function sortRows<R extends Kinded>(columns: GridColumn<R>[], rows: GridDataRow<R>[], sortState: SortState<number>): void;
4
+ export declare function sortRows<R extends Kinded>(columns: GridColumn<R>[], rows: GridDataRow<R>[], sortState: SortState<number>): GridDataRow<R>[];
5
5
  export declare function ensureClientSideSortValueIsSortable(sorting: GridSortConfig<any> | undefined, isHeader: boolean, column: GridColumn<any>, idx: number, maybeContent: ReactNode | GridCellContent): void;
@@ -2,21 +2,16 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ensureClientSideSortValueIsSortable = exports.sortRows = void 0;
4
4
  const GridTable_1 = require("./GridTable");
5
- // We currently mutate `rows` while sorting; this would be bad if rows was directly
6
- // read from an immutable store like the apollo cache, but we basically always make
7
- // a copy in the process of adding our `kind` tags.
8
- //
9
- // I suppose that is an interesting idea, would we ever want to render a GQL query/cache
10
- // result directly into the table without first doing a kind-mapping? Like maybe we could
11
- // use __typename as the kind.
5
+ // Returns a shallow copy of the `rows` parameter sorted based on `sortState`
12
6
  function sortRows(columns, rows, sortState) {
13
- sortBatch(columns, rows, sortState);
7
+ const sorted = sortBatch(columns, rows, sortState);
14
8
  // Recursively sort child rows
15
- for (const row of rows) {
9
+ sorted.forEach((row, i) => {
16
10
  if (row.children) {
17
- sortRows(columns, row.children, sortState);
11
+ sorted[i] = { ...sorted[i], children: sortRows(columns, row.children, sortState) };
18
12
  }
19
- }
13
+ });
14
+ return sorted;
20
15
  }
21
16
  exports.sortRows = sortRows;
22
17
  function sortBatch(columns, batch, sortState) {
@@ -24,7 +19,8 @@ function sortBatch(columns, batch, sortState) {
24
19
  const [value, direction] = sortState;
25
20
  const column = columns[value];
26
21
  const invert = direction === "DESC";
27
- batch.sort((a, b) => {
22
+ // Make a shallow copy for sorting to avoid mutating the original list
23
+ return [...batch].sort((a, b) => {
28
24
  const v1 = sortValue((0, GridTable_1.applyRowFn)(column, a));
29
25
  const v2 = sortValue((0, GridTable_1.applyRowFn)(column, b));
30
26
  const v1e = v1 === null || v1 === undefined;
@@ -10,3 +10,4 @@ import { Direction, GridColumn, GridSortConfig, Kinded } from "./GridTable";
10
10
  export declare type SortState<S> = readonly [S, Direction];
11
11
  /** Small custom hook that wraps the "setSortColumn inverts the current sort" logic. */
12
12
  export declare function useSortState<R extends Kinded, S>(columns: GridColumn<R, S>[], sorting?: GridSortConfig<S>): [SortState<S> | undefined, (value: S) => void];
13
+ export declare function deriveSortState<S>(currentSortState: SortState<S> | undefined, clickedKey: S, initialSortState: SortState<S> | undefined): SortState<S> | undefined;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useSortState = void 0;
3
+ exports.deriveSortState = exports.useSortState = void 0;
4
4
  const react_1 = require("react");
5
5
  const GridTable_1 = require("./GridTable");
6
6
  /** Small custom hook that wraps the "setSortColumn inverts the current sort" logic. */
@@ -8,34 +8,34 @@ function useSortState(columns, sorting) {
8
8
  // If we're server-side sorting, use the caller's `sorting.value` prop to initialize our internal
9
9
  // `useState`. After this, we ignore `sorting.value` because we assume it should match what our
10
10
  // `setSortState` just changed anyway (in response to the user sorting a column).
11
- const [sortState, setSortState] = (0, react_1.useState)(() => {
11
+ const initialSortState = (0, react_1.useMemo)(() => {
12
12
  if ((sorting === null || sorting === void 0 ? void 0 : sorting.on) === "client") {
13
13
  const { initial } = sorting;
14
- if (initial) {
14
+ if (initial === undefined && "initial" in sorting) {
15
+ // if explicitly set to `undefined`, then do not sort
16
+ return undefined;
17
+ }
18
+ else if (initial) {
15
19
  const key = typeof initial[0] === "number" ? initial[0] : columns.indexOf(initial[0]);
16
20
  return [key, initial[1]];
17
21
  }
18
22
  else {
19
23
  // If no explicit sorting, assume 1st column ascending
20
24
  const firstSortableColumn = columns.findIndex((c) => c.clientSideSort !== false);
21
- return [firstSortableColumn, "ASC"];
25
+ return [firstSortableColumn, GridTable_1.ASC];
22
26
  }
23
27
  }
24
28
  else {
25
29
  return sorting === null || sorting === void 0 ? void 0 : sorting.value;
26
30
  }
27
- });
28
- // Make a custom setSortKey that is useState-like but contains the invert-if-same-column-clicked-twice logic.
31
+ }, [sorting, columns]);
32
+ const [sortState, setSortState] = (0, react_1.useState)(initialSortState);
33
+ // Make a custom setSortKey that is useState-like but contains the ASC->DESC->RESET logic.
29
34
  const setSortKey = (0, react_1.useCallback)((clickedKey) => {
30
- const [currentKey, currentDirection] = sortState || [];
31
- const [newKey, newDirection] =
32
- // If clickedKey === currentKey, then toggle direction
33
- clickedKey === currentKey
34
- ? [currentKey, currentDirection === GridTable_1.ASC ? GridTable_1.DESC : GridTable_1.ASC]
35
- : // Otherwise, use the new key, and default to ascending
36
- [clickedKey, GridTable_1.ASC];
37
- setSortState([newKey, newDirection]);
35
+ const newState = deriveSortState(sortState, clickedKey, initialSortState);
36
+ setSortState(newState);
38
37
  if ((sorting === null || sorting === void 0 ? void 0 : sorting.on) === "server") {
38
+ const [newKey, newDirection] = newState !== null && newState !== void 0 ? newState : [undefined, undefined];
39
39
  sorting.onSort(newKey, newDirection);
40
40
  }
41
41
  },
@@ -44,3 +44,19 @@ function useSortState(columns, sorting) {
44
44
  return [sortState, setSortKey];
45
45
  }
46
46
  exports.useSortState = useSortState;
47
+ // Exported for testing purposes
48
+ function deriveSortState(currentSortState, clickedKey, initialSortState) {
49
+ const [currentKey, currentDirection] = currentSortState || [];
50
+ // If the current sort state is not defined, or clicking a new column, then sort ASC on the clicked key
51
+ if (!currentSortState || clickedKey !== currentKey) {
52
+ return [clickedKey, GridTable_1.ASC];
53
+ }
54
+ // Otherwise when clicking the current column, toggle through sort states
55
+ if (currentDirection === GridTable_1.ASC) {
56
+ // if ASC -> go to desc
57
+ return [clickedKey, GridTable_1.DESC];
58
+ }
59
+ // Else, direction is already DESC, so revert to original sort value.
60
+ return initialSortState;
61
+ }
62
+ exports.deriveSortState = deriveSortState;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homebound/beam",
3
- "version": "2.99.1",
3
+ "version": "2.101.2",
4
4
  "author": "Homebound",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",