@homebound/beam 2.106.1 → 2.106.4

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.
@@ -4,21 +4,18 @@ exports.CollapseToggle = void 0;
4
4
  const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
5
5
  const react_1 = require("react");
6
6
  const components_1 = require("..");
7
+ const hooks_1 = require("../../hooks");
7
8
  function CollapseToggle(props) {
8
9
  const { row } = props;
9
- const { isCollapsed, toggleCollapsed } = (0, react_1.useContext)(components_1.GridCollapseContext);
10
- const [, setTick] = (0, react_1.useState)(0);
11
- const currentlyCollapsed = isCollapsed(row.id);
12
- const toggleOnClick = (0, react_1.useCallback)(() => {
13
- toggleCollapsed(row.id);
14
- setTick(Date.now());
15
- }, [row.id, currentlyCollapsed, toggleCollapsed]);
16
- const iconKey = currentlyCollapsed ? "chevronRight" : "chevronDown";
17
- const headerIconKey = currentlyCollapsed ? "chevronsRight" : "chevronsDown";
10
+ const { rowState } = (0, react_1.useContext)(components_1.RowStateContext);
11
+ const isCollapsed = (0, hooks_1.useComputed)(() => rowState.isCollapsed(row.id), [rowState]);
12
+ const iconKey = isCollapsed ? "chevronRight" : "chevronDown";
13
+ const headerIconKey = isCollapsed ? "chevronsRight" : "chevronsDown";
14
+ // If we're not a header, only render a toggle if we have child rows to actually collapse
18
15
  const isHeader = row.kind === "header";
19
16
  if (!isHeader && (!props.row.children || props.row.children.length === 0)) {
20
17
  return null;
21
18
  }
22
- return (0, jsx_runtime_1.jsx)(components_1.IconButton, { onClick: toggleOnClick, icon: isHeader ? headerIconKey : iconKey }, void 0);
19
+ return (0, jsx_runtime_1.jsx)(components_1.IconButton, { onClick: () => rowState.toggleCollapsed(row.id), icon: isHeader ? headerIconKey : iconKey }, void 0);
23
20
  }
24
21
  exports.CollapseToggle = CollapseToggle;
@@ -1,4 +1,4 @@
1
- import React, { MutableRefObject, ReactElement, ReactNode } from "react";
1
+ import { MutableRefObject, ReactElement, ReactNode } from "react";
2
2
  import { PresentationContextProps, PresentationFieldProps } from "../PresentationContext";
3
3
  import { GridRowLookup } from "./GridRowLookup";
4
4
  import { Margin, Only, Properties, Typography, Xss } from "../../Css";
@@ -301,21 +301,5 @@ export declare type GridDataRow<R extends Kinded> = {
301
301
  } & DiscriminateUnion<R, "kind", R["kind"]>;
302
302
  /** Return the content for a given column def applied to a given row. */
303
303
  export declare function applyRowFn<R extends Kinded>(column: GridColumn<R>, row: GridDataRow<R>): ReactNode | GridCellContent;
304
- /**
305
- * Provides each row access to a method to check if it is collapsed and toggle it's collapsed state.
306
- *
307
- * Calling `toggleCollapse` will keep the row itself showing, but will hide any
308
- * children rows (specifically those that have this row's `id` in their `parentIds`
309
- * prop).
310
- *
311
- * headerCollapsed is used to trigger rows at the root level to rerender their chevron when all are
312
- * collapsed/expanded.
313
- */
314
- declare type GridCollapseContextProps = {
315
- headerCollapsed: boolean;
316
- isCollapsed: (id: string) => boolean;
317
- toggleCollapsed(id: string): void;
318
- };
319
- export declare const GridCollapseContext: React.Context<GridCollapseContextProps>;
320
304
  export declare function matchesFilter(maybeContent: ReactNode | GridCellContent, filter: string): boolean;
321
305
  export {};
@@ -22,9 +22,8 @@ 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.calcColumnSizes = exports.GridTable = exports.setGridTableDefaults = exports.setDefaultStyle = exports.setRunningInJest = exports.emptyCell = exports.DESC = exports.ASC = void 0;
25
+ exports.matchesFilter = 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
- const utils_1 = require("@react-aria/utils");
28
27
  const memoize_one_1 = __importDefault(require("memoize-one"));
29
28
  const mobx_react_1 = require("mobx-react");
30
29
  const react_1 = __importStar(require("react"));
@@ -32,15 +31,17 @@ const react_router_dom_1 = require("react-router-dom");
32
31
  const react_virtuoso_1 = require("react-virtuoso");
33
32
  const CssReset_1 = require("../CssReset");
34
33
  const PresentationContext_1 = require("../PresentationContext");
34
+ const columnSizes_1 = require("./columnSizes");
35
35
  const GridRowLookup_1 = require("./GridRowLookup");
36
36
  const GridSortContext_1 = require("./GridSortContext");
37
37
  const nestedCards_1 = require("./nestedCards");
38
+ const RowState_1 = require("./RowState");
38
39
  const SortHeader_1 = require("./SortHeader");
39
40
  const sortRows_1 = require("./sortRows");
40
41
  const useSortState_1 = require("./useSortState");
41
42
  const Css_1 = require("../../Css");
43
+ const hooks_1 = require("../../hooks");
42
44
  const tinycolor2_1 = __importDefault(require("tinycolor2"));
43
- const use_debounce_1 = require("use-debounce");
44
45
  const _1 = require(".");
45
46
  exports.ASC = "ASC";
46
47
  exports.DESC = "DESC";
@@ -82,9 +83,12 @@ exports.setGridTableDefaults = setGridTableDefaults;
82
83
  * special styling to the row that uses `kind: "header"`.)
83
84
  */
84
85
  function GridTable(props) {
85
- var _a, _b, _c, _d, _e;
86
+ var _a, _b, _c, _d;
86
87
  const { id = "gridTable", as = "div", columns, rows, style = defaults.style, rowStyles, stickyHeader = defaults.stickyHeader, stickyOffset = "0", xss, sorting, filter, filterMaxRows, fallbackMessage = "No rows found.", infoMessage, setRowCount, observeRows, persistCollapse, api, resizeTarget, } = props;
87
- const [collapsedIds, collapseAllContext, collapseRowContext] = useToggleIds(rows, persistCollapse);
88
+ // Create a ref that always contains the latest rows, for our effectively-singleton RowState to use
89
+ const rowsRef = (0, react_1.useRef)(rows);
90
+ rowsRef.current = rows;
91
+ const [rowState] = (0, react_1.useState)(() => new RowState_1.RowState(rowsRef, persistCollapse));
88
92
  // We only use this in as=virtual mode, but keep this here for rowLookup to use
89
93
  const virtuosoRef = (0, react_1.useRef)(null);
90
94
  const tableRef = (0, react_1.useRef)(null);
@@ -101,37 +105,9 @@ function GridTable(props) {
101
105
  }
102
106
  return rows;
103
107
  }, [columns, rows, sorting, sortState]);
104
- // Calculate the column sizes immediately rather than via the `debounce` method.
105
- // We do this for Storybook integrations that may use MockDate. MockDate changes the behavior of `new Date()`, which is used by `useDebounce` and essentially turns off the callback.
106
- const calculateImmediately = (0, react_1.useRef)(true);
107
- const [tableWidth, setTableWidth] = (0, react_1.useState)();
108
- // Calc our initial/first render sizes where we won't have a width yet
109
- const [columnSizes, setColumnSizes] = (0, react_1.useState)(calcColumnSizes(columns, (_a = style.nestedCards) === null || _a === void 0 ? void 0 : _a.firstLastColumnWidth, tableWidth, style.minWidthPx));
110
- const setTableAndColumnWidths = (0, react_1.useCallback)((width) => {
111
- var _a;
112
- setTableWidth(width);
113
- setColumnSizes(calcColumnSizes(columns, (_a = style.nestedCards) === null || _a === void 0 ? void 0 : _a.firstLastColumnWidth, width, style.minWidthPx));
114
- }, [setTableWidth, setColumnSizes, columns, style]);
115
- const setTableAndColumnWidthsDebounced = (0, use_debounce_1.useDebouncedCallback)(setTableAndColumnWidths, 100);
116
- const onResize = (0, react_1.useCallback)(() => {
117
- const target = (resizeTarget === null || resizeTarget === void 0 ? void 0 : resizeTarget.current) ? resizeTarget.current : tableRef.current;
118
- if (target && target.clientWidth !== tableWidth) {
119
- if (calculateImmediately.current) {
120
- calculateImmediately.current = false;
121
- setTableAndColumnWidths(target.clientWidth);
122
- }
123
- else {
124
- setTableAndColumnWidthsDebounced(target.clientWidth);
125
- }
126
- }
127
- }, [
128
- resizeTarget === null || resizeTarget === void 0 ? void 0 : resizeTarget.current,
129
- tableRef.current,
130
- setTableAndColumnWidths,
131
- calculateImmediately,
132
- setTableAndColumnWidthsDebounced,
133
- ]);
134
- (0, utils_1.useResizeObserver)({ ref: resizeTarget !== null && resizeTarget !== void 0 ? resizeTarget : tableRef, onResize });
108
+ const columnSizes = (0, columnSizes_1.useSetupColumnSizes)(style, columns, tableRef, resizeTarget);
109
+ // Make a single copy of our current collapsed state, so we'll have a single observer.
110
+ const collapsedIds = (0, hooks_1.useComputed)(() => rowState.collapsedIds, [rowState]);
135
111
  // Filter + flatten + component-ize the sorted rows.
136
112
  let [headerRows, filteredRows] = (0, react_1.useMemo)(() => {
137
113
  // Break up "foo bar" into `[foo, bar]` and a row must match both `foo` and `bar`
@@ -141,19 +117,19 @@ function GridTable(props) {
141
117
  // changes, and so by not passing the sortProps, it means the data rows' React.memo will still cache them.
142
118
  const sortProps = row.kind === "header" ? { sorting, sortState, setSortKey } : { sorting };
143
119
  const RowComponent = observeRows ? ObservedGridRow : MemoizedGridRow;
144
- return ((0, jsx_runtime_1.jsx)(exports.GridCollapseContext.Provider, Object.assign({ value: row.kind === "header" ? collapseAllContext : collapseRowContext }, { children: (0, jsx_runtime_1.jsx)(RowComponent, Object.assign({}, {
145
- as,
146
- columns,
147
- row,
148
- style,
149
- rowStyles,
150
- stickyHeader,
151
- stickyOffset,
152
- openCards: nestedCards ? nestedCards.currentOpenCards() : undefined,
153
- columnSizes,
154
- level,
155
- ...sortProps,
156
- }), void 0) }), `${row.kind}-${row.id}`));
120
+ return ((0, jsx_runtime_1.jsx)(RowComponent, Object.assign({}, {
121
+ as,
122
+ columns,
123
+ row,
124
+ style,
125
+ rowStyles,
126
+ stickyHeader,
127
+ stickyOffset,
128
+ openCards: nestedCards ? nestedCards.currentOpenCards() : undefined,
129
+ columnSizes,
130
+ level,
131
+ ...sortProps,
132
+ }), `${row.kind}-${row.id}`));
157
133
  }
158
134
  // Split out the header rows from the data rows so that we can put an `infoMessage` in between them (if needed).
159
135
  const headerRows = [];
@@ -166,8 +142,8 @@ function GridTable(props) {
166
142
  const matches = filters.length === 0 ||
167
143
  row.pin ||
168
144
  filters.every((filter) => columns.map((c) => applyRowFn(c, row)).some((maybeContent) => matchesFilter(maybeContent, filter)));
169
- // Even if we don't pass the filter, one of our children might, so we continue on after this check
170
145
  let isCard = false;
146
+ // Even if we don't pass the filter, one of our children might, so we continue on after this check
171
147
  if (matches) {
172
148
  isCard = nestedCards && nestedCards.maybeOpenCard(row);
173
149
  filteredRows.push([row, makeRowComponent(row, level)]);
@@ -206,11 +182,9 @@ function GridTable(props) {
206
182
  sortState,
207
183
  stickyHeader,
208
184
  stickyOffset,
209
- collapsedIds,
210
- collapseAllContext,
211
- collapseRowContext,
212
185
  observeRows,
213
186
  columnSizes,
187
+ collapsedIds,
214
188
  ]);
215
189
  let tooManyClientSideRows = false;
216
190
  if (filterMaxRows && filteredRows.length > filterMaxRows) {
@@ -228,8 +202,8 @@ function GridTable(props) {
228
202
  }, [filteredRows === null || filteredRows === void 0 ? void 0 : filteredRows.length, setRowCount]);
229
203
  const noData = filteredRows.length === 0;
230
204
  const firstRowMessage = (noData && fallbackMessage) || (tooManyClientSideRows && "Hiding some rows, use filter...") || infoMessage;
231
- const borderless = (_b = style === null || style === void 0 ? void 0 : style.presentationSettings) === null || _b === void 0 ? void 0 : _b.borderless;
232
- const typeScale = (_c = style === null || style === void 0 ? void 0 : style.presentationSettings) === null || _c === void 0 ? void 0 : _c.typeScale;
205
+ const borderless = (_a = style === null || style === void 0 ? void 0 : style.presentationSettings) === null || _a === void 0 ? void 0 : _a.borderless;
206
+ const typeScale = (_b = style === null || style === void 0 ? void 0 : style.presentationSettings) === null || _b === void 0 ? void 0 : _b.typeScale;
233
207
  const fieldProps = (0, react_1.useMemo)(() => ({
234
208
  hideLabel: true,
235
209
  numberAlignment: "right",
@@ -243,7 +217,8 @@ function GridTable(props) {
243
217
  // just trust the GridTable impl that, at runtime, `as=virtual` will (other than being virtualized)
244
218
  // behave semantically the same as `as=div` did for its tests.
245
219
  const _as = as === "virtual" && runningInJest ? "div" : as;
246
- return ((0, jsx_runtime_1.jsx)(PresentationContext_1.PresentationProvider, Object.assign({ fieldProps: fieldProps, wrap: (_d = style === null || style === void 0 ? void 0 : style.presentationSettings) === null || _d === void 0 ? void 0 : _d.wrap }, { children: renders[_as](style, id, columns, headerRows, filteredRows, firstRowMessage, stickyHeader, (_e = style.nestedCards) === null || _e === void 0 ? void 0 : _e.firstLastColumnWidth, xss, virtuosoRef, tableRef) }), void 0));
220
+ const rowStateContext = (0, react_1.useMemo)(() => ({ rowState }), [rowState]);
221
+ return ((0, jsx_runtime_1.jsx)(RowState_1.RowStateContext.Provider, Object.assign({ value: rowStateContext }, { children: (0, jsx_runtime_1.jsx)(PresentationContext_1.PresentationProvider, Object.assign({ fieldProps: fieldProps, wrap: (_c = style === null || style === void 0 ? void 0 : style.presentationSettings) === null || _c === void 0 ? void 0 : _c.wrap }, { children: renders[_as](style, id, columns, headerRows, filteredRows, firstRowMessage, stickyHeader, (_d = style.nestedCards) === null || _d === void 0 ? void 0 : _d.firstLastColumnWidth, xss, virtuosoRef, tableRef) }), void 0) }), void 0));
247
222
  }
248
223
  exports.GridTable = GridTable;
249
224
  // Determine which HTML element to use to build the GridTable
@@ -494,7 +469,7 @@ function GridRow(props) {
494
469
  // Decrement colspan count and skip if greater than 1.
495
470
  if (currentColspan > 1) {
496
471
  currentColspan -= 1;
497
- return;
472
+ return null;
498
473
  }
499
474
  const maybeContent = applyRowFn(column, row);
500
475
  currentColspan = isGridCellContent(maybeContent) ? (_a = maybeContent.colspan) !== null && _a !== void 0 ? _a : 1 : 1;
@@ -624,11 +599,6 @@ const defaultRenderFn = (as) => (key, css, content) => {
624
599
  const Cell = as === "table" ? "td" : "div";
625
600
  return ((0, jsx_runtime_1.jsx)(Cell, Object.assign({ css: { ...css, ...tableRowStyles(as) } }, { children: content }), key));
626
601
  };
627
- exports.GridCollapseContext = react_1.default.createContext({
628
- headerCollapsed: false,
629
- isCollapsed: () => false,
630
- toggleCollapsed: () => { },
631
- });
632
602
  /** Sets up the `GridContext` so that header cells can access the current sort settings. */
633
603
  const headerRenderFn = (columns, column, sortState, setSortKey, as) => (key, css, content) => {
634
604
  const [currentKey, direction] = sortState || [];
@@ -710,102 +680,8 @@ exports.matchesFilter = matchesFilter;
710
680
  function maybeDarken(color, defaultColor) {
711
681
  return color ? (0, tinycolor2_1.default)(color).darken(4).toString() : defaultColor;
712
682
  }
713
- // Get the rows that are already in the toggled state, so we can keep them toggled
714
- function getCollapsedRows(persistCollapse) {
715
- if (!persistCollapse)
716
- return [];
717
- const collapsedGridRowIds = localStorage.getItem(persistCollapse);
718
- return collapsedGridRowIds ? JSON.parse(collapsedGridRowIds) : [];
719
- }
720
- /**
721
- * A custom hook to manage a list of ids.
722
- *
723
- * What's special about this hook is that we manage a stable identity
724
- * for the `toggleId` function, so that rows that have _not_ toggled
725
- * themselves on/off will have an unchanged callback and so not be
726
- * re-rendered.
727
- *
728
- * That said, when they do trigger a `toggleId`, the stable/"stale" callback
729
- * function should see/update the latest list of values, which is not possible with a
730
- * traditional `useState` hook because it captures the original/stale list identity.
731
- */
732
- function useToggleIds(rows, persistCollapse) {
733
- // Make a list that we will only mutate, so that our callbacks have a stable identity.
734
- const [collapsedIds] = (0, react_1.useState)(getCollapsedRows(persistCollapse));
735
- // Use this to trigger the component to re-render even though we're not calling `setList`
736
- const [tick, setTick] = (0, react_1.useState)("");
737
- // Checking whether something is collapsed does not depend on anything
738
- const isCollapsed = (0, react_1.useCallback)((id) => collapsedIds.includes(id),
739
- // eslint-disable-next-line react-hooks/exhaustive-deps
740
- []);
741
- const collapseAllContext = (0, react_1.useMemo)(() => {
742
- // Create the stable `toggleCollapsed`, i.e. we are purposefully passing an (almost) empty dep list
743
- // Since only toggling all rows required knowledge of what the rows are
744
- const toggleAll = (_id) => {
745
- // We have different behavior when going from expand/collapse all.
746
- const isAllCollapsed = collapsedIds[0] === "header";
747
- collapsedIds.splice(0, collapsedIds.length);
748
- if (isAllCollapsed) {
749
- // Expand all means keep `collapsedIds` empty
750
- }
751
- else {
752
- // Otherwise push `header` on the list as a hint that we're in the collapsed-all state
753
- collapsedIds.push("header");
754
- // Find all non-leaf rows so that toggling "all collapsed" -> "all not collapsed" opens
755
- // the parent rows of any level.
756
- const parentIds = new Set();
757
- const todo = [...rows];
758
- while (todo.length > 0) {
759
- const r = todo.pop();
760
- if (r.children) {
761
- parentIds.add(r.id);
762
- todo.push(...r.children);
763
- }
764
- }
765
- // And then mark all parent rows as collapsed.
766
- collapsedIds.push(...parentIds);
767
- }
768
- if (persistCollapse) {
769
- localStorage.setItem(persistCollapse, JSON.stringify(collapsedIds));
770
- }
771
- // Trigger a re-render
772
- setTick(collapsedIds.join(","));
773
- };
774
- return { headerCollapsed: isCollapsed("header"), isCollapsed, toggleCollapsed: toggleAll };
775
- },
776
- // eslint-disable-next-line react-hooks/exhaustive-deps
777
- [rows]);
778
- const collapseRowContext = (0, react_1.useMemo)(() => {
779
- // Create the stable `toggleCollapsed`, i.e. we are purposefully passing an empty dep list
780
- // Since toggling a single row does not need to know about the other rows
781
- const toggleRow = (id) => {
782
- // This is the regular/non-header behavior to just add/remove the individual row id
783
- const i = collapsedIds.indexOf(id);
784
- if (i === -1) {
785
- collapsedIds.push(id);
786
- }
787
- else {
788
- collapsedIds.splice(i, 1);
789
- }
790
- if (persistCollapse) {
791
- localStorage.setItem(persistCollapse, JSON.stringify(collapsedIds));
792
- }
793
- // Trigger a re-render
794
- setTick(collapsedIds.join(","));
795
- };
796
- return { headerCollapsed: isCollapsed("header"), isCollapsed, toggleCollapsed: toggleRow };
797
- },
798
- // eslint-disable-next-line react-hooks/exhaustive-deps
799
- [collapseAllContext.isCollapsed("header")]);
800
- // Return a copy of the list, b/c we want external useMemos that do explicitly use the
801
- // entire list as a dep to re-render whenever the list is changed (which they need to
802
- // see as new list identity).
803
- // eslint-disable-next-line react-hooks/exhaustive-deps
804
- const copy = (0, react_1.useMemo)(() => [...collapsedIds], [tick, collapsedIds]);
805
- return [copy, collapseAllContext, collapseRowContext];
806
- }
807
- /** GridTable as Table utility to apply <tr> element override styles */
808
- const tableRowStyles = (as, column) => {
683
+ /** GridTable as Table utility to apply <tr> element override styles. */
684
+ function tableRowStyles(as, column) {
809
685
  const thWidth = column === null || column === void 0 ? void 0 : column.w;
810
686
  return as === "table"
811
687
  ? {
@@ -813,4 +689,4 @@ const tableRowStyles = (as, column) => {
813
689
  ...(thWidth ? Css_1.Css.w(thWidth).$ : {}),
814
690
  }
815
691
  : {};
816
- };
692
+ }
@@ -0,0 +1,33 @@
1
+ import React, { MutableRefObject } from "react";
2
+ import { GridDataRow } from "./GridTable";
3
+ /**
4
+ * Stores the collapsed & selected state of rows.
5
+ *
6
+ * I.e. this implements "collapse parent" --> "hides children", and
7
+ * "select parent" --> "select parent + children".
8
+ *
9
+ * There should be a single, stable `RowStateStore` instance per `GridTable`, so
10
+ * that children don't have to re-render even as we incrementally add/remove rows
11
+ * to the table (i.e. the top-level rows identity changes, but each row within it
12
+ * may not).
13
+ *
14
+ * We use mobx ObservableSets/ObservableMaps to drive granular re-rendering of rows/cells
15
+ * that need to change their toggle/select on/off in response to parent/child
16
+ * changes.
17
+ */
18
+ export declare class RowState {
19
+ private rows;
20
+ private persistCollapse;
21
+ private readonly collapsedRows;
22
+ /**
23
+ * Creates the `RowState` for a given `GridTable`.
24
+ */
25
+ constructor(rows: MutableRefObject<GridDataRow<any>[]>, persistCollapse: string | undefined);
26
+ get collapsedIds(): string[];
27
+ isCollapsed(id: string): boolean;
28
+ toggleCollapsed(id: string): void;
29
+ }
30
+ /** Provides a context for rows to access their table's `RowState`. */
31
+ export declare const RowStateContext: React.Context<{
32
+ rowState: RowState;
33
+ }>;
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.RowStateContext = exports.RowState = void 0;
7
+ const mobx_1 = require("mobx");
8
+ const react_1 = __importDefault(require("react"));
9
+ // Will be used in the next PR...
10
+ // A parent row can be partially selected when some children are selected/some aren't.
11
+ // export type SelectedState = "checked" | "unchecked" | "partial";
12
+ /**
13
+ * Stores the collapsed & selected state of rows.
14
+ *
15
+ * I.e. this implements "collapse parent" --> "hides children", and
16
+ * "select parent" --> "select parent + children".
17
+ *
18
+ * There should be a single, stable `RowStateStore` instance per `GridTable`, so
19
+ * that children don't have to re-render even as we incrementally add/remove rows
20
+ * to the table (i.e. the top-level rows identity changes, but each row within it
21
+ * may not).
22
+ *
23
+ * We use mobx ObservableSets/ObservableMaps to drive granular re-rendering of rows/cells
24
+ * that need to change their toggle/select on/off in response to parent/child
25
+ * changes.
26
+ */
27
+ class RowState {
28
+ // Coming in future PR
29
+ // readonly selectedRows = new ObservableMap<string, "checked" | "unchecked" | "partial">();
30
+ /**
31
+ * Creates the `RowState` for a given `GridTable`.
32
+ */
33
+ constructor(rows, persistCollapse) {
34
+ this.rows = rows;
35
+ this.persistCollapse = persistCollapse;
36
+ this.collapsedRows = new mobx_1.ObservableSet(persistCollapse ? readLocalCollapseState(persistCollapse) : []);
37
+ // Make ourselves an observable so that mobx will do caching of .collapseIds so
38
+ // that it'll be a stable identity for GridTable to useMemo against.
39
+ (0, mobx_1.makeAutoObservable)(this, { rows: false }); // as any b/c rows is private, so the mapped type doesn't see it
40
+ }
41
+ get collapsedIds() {
42
+ return [...this.collapsedRows.values()];
43
+ }
44
+ // Should be called in an Observer/useComputed to trigger re-renders
45
+ isCollapsed(id) {
46
+ return this.collapsedRows.has(id) || this.collapsedRows.has("header");
47
+ }
48
+ toggleCollapsed(id) {
49
+ const collapsedIds = [...this.collapsedRows.values()];
50
+ // We have different behavior when going from expand/collapse all.
51
+ if (id === "header") {
52
+ const isAllCollapsed = collapsedIds[0] === "header";
53
+ if (isAllCollapsed) {
54
+ // Expand all means keep `collapsedIds` empty
55
+ collapsedIds.splice(0, collapsedIds.length);
56
+ }
57
+ else {
58
+ // Otherwise push `header` on the list as a hint that we're in the collapsed-all state
59
+ collapsedIds.push("header");
60
+ // Find all non-leaf rows so that toggling "all collapsed" -> "all not collapsed" opens
61
+ // the parent rows of any level.
62
+ const parentIds = new Set();
63
+ const todo = [...this.rows.current];
64
+ while (todo.length > 0) {
65
+ const r = todo.pop();
66
+ if (r.children) {
67
+ parentIds.add(r.id);
68
+ todo.push(...r.children);
69
+ }
70
+ }
71
+ // And then mark all parent rows as collapsed.
72
+ collapsedIds.push(...parentIds);
73
+ }
74
+ }
75
+ else {
76
+ // This is the regular/non-header behavior to just add/remove the individual row id
77
+ const i = collapsedIds.indexOf(id);
78
+ if (i === -1) {
79
+ collapsedIds.push(id);
80
+ }
81
+ else {
82
+ collapsedIds.splice(i, 1);
83
+ }
84
+ }
85
+ this.collapsedRows.replace(collapsedIds);
86
+ if (this.persistCollapse) {
87
+ localStorage.setItem(this.persistCollapse, JSON.stringify(collapsedIds));
88
+ }
89
+ }
90
+ }
91
+ exports.RowState = RowState;
92
+ /** Provides a context for rows to access their table's `RowState`. */
93
+ exports.RowStateContext = react_1.default.createContext({
94
+ get rowState() {
95
+ throw new Error("No RowStateContext provider");
96
+ },
97
+ });
98
+ // Get the rows that are already in the toggled state, so we can keep them toggled
99
+ function readLocalCollapseState(persistCollapse) {
100
+ const collapsedGridRowIds = localStorage.getItem(persistCollapse);
101
+ return collapsedGridRowIds ? JSON.parse(collapsedGridRowIds) : [];
102
+ }
@@ -0,0 +1,22 @@
1
+ import { MutableRefObject } from "react";
2
+ import { GridColumn, GridStyle } from "./GridTable";
3
+ /**
4
+ * Calculates an array of sizes for each of our columns.
5
+ *
6
+ * We originally supported CSS grid-template-column definitions which allowed fancier,
7
+ * dynamic/content-based widths, but have eventually dropped it mainly due to:
8
+ *
9
+ * 1. In virtual tables, a) the table never has all of the rows in DOM at a single time,
10
+ * so any "content-based" widths will change as you scroll the table, which is weird, and
11
+ * b) a sticky header and rows are put in different DOM parent elements by react-virtuoso,
12
+ * so wouldn't arrive at the same "content-based" widths.
13
+ *
14
+ * 2. Using CSS grid but still have a row-level div for hover/focus targeting required
15
+ * a "fake" `display: contents` div that couldn't have actually any styles applied to it.
16
+ *
17
+ * So we've just got with essentially fixed/deterministic widths, i.e. `px` or `percent` or
18
+ * `fr`.
19
+ *
20
+ * Disclaimer that we roll our own `fr` b/c we're not in CSS grid anymore.
21
+ */
22
+ export declare function useSetupColumnSizes(style: GridStyle, columns: GridColumn<any>[], tableRef: MutableRefObject<HTMLElement | null>, resizeTarget: MutableRefObject<HTMLElement | null> | undefined): string[];
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useSetupColumnSizes = void 0;
4
+ const utils_1 = require("@react-aria/utils");
5
+ const react_1 = require("react");
6
+ const GridTable_1 = require("./GridTable");
7
+ const use_debounce_1 = require("use-debounce");
8
+ /**
9
+ * Calculates an array of sizes for each of our columns.
10
+ *
11
+ * We originally supported CSS grid-template-column definitions which allowed fancier,
12
+ * dynamic/content-based widths, but have eventually dropped it mainly due to:
13
+ *
14
+ * 1. In virtual tables, a) the table never has all of the rows in DOM at a single time,
15
+ * so any "content-based" widths will change as you scroll the table, which is weird, and
16
+ * b) a sticky header and rows are put in different DOM parent elements by react-virtuoso,
17
+ * so wouldn't arrive at the same "content-based" widths.
18
+ *
19
+ * 2. Using CSS grid but still have a row-level div for hover/focus targeting required
20
+ * a "fake" `display: contents` div that couldn't have actually any styles applied to it.
21
+ *
22
+ * So we've just got with essentially fixed/deterministic widths, i.e. `px` or `percent` or
23
+ * `fr`.
24
+ *
25
+ * Disclaimer that we roll our own `fr` b/c we're not in CSS grid anymore.
26
+ */
27
+ function useSetupColumnSizes(style, columns, tableRef, resizeTarget) {
28
+ var _a, _b;
29
+ // Calculate the column sizes immediately rather than via the `debounce` method.
30
+ // We do this for Storybook integrations that may use MockDate. MockDate changes the behavior of `new Date()`,
31
+ // which is used internally by `useDebounce`, so the frozen clock means the callback is never called.
32
+ const calculateImmediately = (0, react_1.useRef)(true);
33
+ const [tableWidth, setTableWidth] = (0, react_1.useState)();
34
+ // Calc our initial/first render sizes where we won't have a width yet
35
+ const [columnSizes, setColumnSizes] = (0, react_1.useState)(
36
+ // TODO Add a useEffect to re-calc this on change
37
+ (0, GridTable_1.calcColumnSizes)(columns, (_a = style.nestedCards) === null || _a === void 0 ? void 0 : _a.firstLastColumnWidth, tableWidth, style.minWidthPx));
38
+ const setTableAndColumnWidths = (0, react_1.useCallback)((width) => {
39
+ var _a;
40
+ setTableWidth(width);
41
+ setColumnSizes((0, GridTable_1.calcColumnSizes)(columns, (_a = style.nestedCards) === null || _a === void 0 ? void 0 : _a.firstLastColumnWidth, width, style.minWidthPx));
42
+ }, [setTableWidth, setColumnSizes, columns, style]);
43
+ const setTableAndColumnWidthsDebounced = (0, use_debounce_1.useDebouncedCallback)(setTableAndColumnWidths, 100);
44
+ const target = (_b = resizeTarget === null || resizeTarget === void 0 ? void 0 : resizeTarget.current) !== null && _b !== void 0 ? _b : tableRef.current;
45
+ const onResize = (0, react_1.useCallback)(() => {
46
+ if (target && target.clientWidth !== tableWidth) {
47
+ if (calculateImmediately.current) {
48
+ calculateImmediately.current = false;
49
+ setTableAndColumnWidths(target.clientWidth);
50
+ }
51
+ else {
52
+ setTableAndColumnWidthsDebounced(target.clientWidth);
53
+ }
54
+ }
55
+ }, [target, tableWidth, setTableAndColumnWidths, setTableAndColumnWidthsDebounced]);
56
+ (0, utils_1.useResizeObserver)({ ref: resizeTarget !== null && resizeTarget !== void 0 ? resizeTarget : tableRef, onResize });
57
+ return columnSizes;
58
+ }
59
+ exports.useSetupColumnSizes = useSetupColumnSizes;
@@ -3,8 +3,9 @@ export * from "./columns";
3
3
  export type { GridRowLookup } from "./GridRowLookup";
4
4
  export { GridSortContext } from "./GridSortContext";
5
5
  export { ASC, DESC, GridTable, setDefaultStyle, setGridTableDefaults } from "./GridTable";
6
- export type { Direction, GridCellAlignment, GridCellContent, GridCollapseContext, GridColumn, GridDataRow, GridRowStyles, GridSortConfig, GridStyle, GridTableDefaults, GridTableProps, GridTableXss, Kinded, RowStyle, setRunningInJest, } from "./GridTable";
6
+ export type { Direction, GridCellAlignment, GridCellContent, GridColumn, GridDataRow, GridRowStyles, GridSortConfig, GridStyle, GridTableDefaults, GridTableProps, GridTableXss, Kinded, RowStyle, setRunningInJest, } from "./GridTable";
7
+ export { RowState, RowStateContext } from "./RowState";
7
8
  export { simpleDataRows, simpleHeader, simpleRows } from "./simpleHelpers";
8
9
  export type { SimpleHeaderAndDataOf, SimpleHeaderAndDataWith } from "./simpleHelpers";
9
10
  export { SortHeader } from "./SortHeader";
10
- export { beamFixedStyle, beamFlexibleStyle, beamNestedFixedStyle, beamNestedFlexibleStyle, cardStyle, condensedStyle, defaultStyle, } from "./styles";
11
+ export { beamFixedStyle, beamFlexibleStyle, beamNestedFixedStyle, beamNestedFlexibleStyle, beamTotalsFixedStyle, beamTotalsFlexibleStyle, cardStyle, condensedStyle, defaultStyle, } from "./styles";
@@ -10,7 +10,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
10
10
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
11
11
  };
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
- exports.defaultStyle = exports.condensedStyle = exports.cardStyle = exports.beamNestedFlexibleStyle = exports.beamNestedFixedStyle = exports.beamFlexibleStyle = exports.beamFixedStyle = exports.SortHeader = exports.simpleRows = exports.simpleHeader = exports.simpleDataRows = exports.setGridTableDefaults = exports.setDefaultStyle = exports.GridTable = exports.DESC = exports.ASC = exports.GridSortContext = void 0;
13
+ exports.defaultStyle = exports.condensedStyle = exports.cardStyle = exports.beamTotalsFlexibleStyle = exports.beamTotalsFixedStyle = exports.beamNestedFlexibleStyle = exports.beamNestedFixedStyle = exports.beamFlexibleStyle = exports.beamFixedStyle = exports.SortHeader = exports.simpleRows = exports.simpleHeader = exports.simpleDataRows = exports.RowStateContext = exports.RowState = exports.setGridTableDefaults = exports.setDefaultStyle = exports.GridTable = exports.DESC = exports.ASC = exports.GridSortContext = void 0;
14
14
  __exportStar(require("./CollapseToggle"), exports);
15
15
  __exportStar(require("./columns"), exports);
16
16
  var GridSortContext_1 = require("./GridSortContext");
@@ -21,6 +21,9 @@ Object.defineProperty(exports, "DESC", { enumerable: true, get: function () { re
21
21
  Object.defineProperty(exports, "GridTable", { enumerable: true, get: function () { return GridTable_1.GridTable; } });
22
22
  Object.defineProperty(exports, "setDefaultStyle", { enumerable: true, get: function () { return GridTable_1.setDefaultStyle; } });
23
23
  Object.defineProperty(exports, "setGridTableDefaults", { enumerable: true, get: function () { return GridTable_1.setGridTableDefaults; } });
24
+ var RowState_1 = require("./RowState");
25
+ Object.defineProperty(exports, "RowState", { enumerable: true, get: function () { return RowState_1.RowState; } });
26
+ Object.defineProperty(exports, "RowStateContext", { enumerable: true, get: function () { return RowState_1.RowStateContext; } });
24
27
  var simpleHelpers_1 = require("./simpleHelpers");
25
28
  Object.defineProperty(exports, "simpleDataRows", { enumerable: true, get: function () { return simpleHelpers_1.simpleDataRows; } });
26
29
  Object.defineProperty(exports, "simpleHeader", { enumerable: true, get: function () { return simpleHelpers_1.simpleHeader; } });
@@ -32,6 +35,8 @@ Object.defineProperty(exports, "beamFixedStyle", { enumerable: true, get: functi
32
35
  Object.defineProperty(exports, "beamFlexibleStyle", { enumerable: true, get: function () { return styles_1.beamFlexibleStyle; } });
33
36
  Object.defineProperty(exports, "beamNestedFixedStyle", { enumerable: true, get: function () { return styles_1.beamNestedFixedStyle; } });
34
37
  Object.defineProperty(exports, "beamNestedFlexibleStyle", { enumerable: true, get: function () { return styles_1.beamNestedFlexibleStyle; } });
38
+ Object.defineProperty(exports, "beamTotalsFixedStyle", { enumerable: true, get: function () { return styles_1.beamTotalsFixedStyle; } });
39
+ Object.defineProperty(exports, "beamTotalsFlexibleStyle", { enumerable: true, get: function () { return styles_1.beamTotalsFlexibleStyle; } });
35
40
  Object.defineProperty(exports, "cardStyle", { enumerable: true, get: function () { return styles_1.cardStyle; } });
36
41
  Object.defineProperty(exports, "condensedStyle", { enumerable: true, get: function () { return styles_1.condensedStyle; } });
37
42
  Object.defineProperty(exports, "defaultStyle", { enumerable: true, get: function () { return styles_1.defaultStyle; } });
@@ -25,7 +25,7 @@ require("trix/dist/trix.css");
25
25
  function RichTextField(props) {
26
26
  const { mergeTags, label, value = "", onChange, onBlur = utils_1.noop, onFocus = utils_1.noop, readOnly } = props;
27
27
  // We get a reference to the Editor instance after trix-init fires
28
- const editor = (0, react_2.useRef)(undefined);
28
+ const [editor, setEditor] = (0, react_2.useState)();
29
29
  const editorElement = (0, react_2.useRef)();
30
30
  // Keep track of what we pass to onChange, so that we can make ourselves keep looking
31
31
  // like a controlled input, i.e. by only calling loadHTML if a new incoming `value` !== `currentHtml`,
@@ -48,12 +48,13 @@ function RichTextField(props) {
48
48
  const targetEl = e.target;
49
49
  if (targetEl.id === id) {
50
50
  editorElement.current = targetEl;
51
- editor.current = editorElement.current.editor;
51
+ const editor = editorElement.current.editor;
52
+ setEditor(editor);
52
53
  if (mergeTags !== undefined) {
53
54
  attachTributeJs(mergeTags, editorElement.current);
54
55
  }
55
56
  currentHtml.current = value;
56
- editor.current.loadHTML(value || "");
57
+ editor.loadHTML(value || "");
57
58
  // Remove listener once we've initialized
58
59
  window.removeEventListener("trix-initialize", onEditorInit);
59
60
  function trixChange(e) {
@@ -88,10 +89,10 @@ function RichTextField(props) {
88
89
  }, []);
89
90
  (0, react_2.useEffect)(() => {
90
91
  // If our value prop changes (without the change coming from us), reload it
91
- if (!readOnly && editor.current && value !== currentHtml.current) {
92
- editor.current.loadHTML(value || "");
92
+ if (!readOnly && editor && value !== currentHtml.current) {
93
+ editor.loadHTML(value || "");
93
94
  }
94
- }, [value, readOnly]);
95
+ }, [value, readOnly, editor]);
95
96
  const { placeholder, autoFocus } = props;
96
97
  if (!readOnly) {
97
98
  return ((0, jsx_runtime_1.jsxs)("div", Object.assign({ css: Css_1.Css.w100.maxw("550px").$ }, { children: [label && (0, jsx_runtime_1.jsx)(Label_1.Label, { labelProps: {}, label: label }, void 0), (0, jsx_runtime_1.jsxs)("div", Object.assign({ css: { ...Css_1.Css.br4.bgWhite.$, ...trixCssOverrides } }, { children: [(0, jsx_runtime_1.jsx)("input", { type: "hidden", id: `input-${id}`, value: value }, void 0), (0, react_2.createElement)("trix-editor", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homebound/beam",
3
- "version": "2.106.1",
3
+ "version": "2.106.4",
4
4
  "author": "Homebound",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",