@homebound/beam 2.98.0 → 2.99.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.
@@ -1,4 +1,4 @@
1
- import { ReactNode } from "react";
1
+ import { MutableRefObject, ReactNode } from "react";
2
2
  import { Only, Xss } from "../../Css";
3
3
  import { Callback } from "../../types";
4
4
  export declare type ModalSize = "sm" | "md" | "lg" | "xl";
@@ -19,7 +19,12 @@ export interface ModalProps {
19
19
  forceScrolling?: boolean;
20
20
  /** Adds a callback that is called _after_ close has definitely happened. */
21
21
  onClose?: Callback;
22
+ /** Imperative API for interacting with the Modal */
23
+ api?: MutableRefObject<ModalApi | undefined>;
22
24
  }
25
+ export declare type ModalApi = {
26
+ setSize: (size: ModalProps["size"]) => void;
27
+ };
23
28
  /**
24
29
  * Internal component for displaying a Modal; see `useModal` for the public API.
25
30
  *
@@ -20,7 +20,7 @@ const utils_1 = require("../../utils");
20
20
  * Provides underlay, modal container, and header. Will disable scrolling of page under the modal.
21
21
  */
22
22
  function Modal(props) {
23
- const { size = "md", content, forceScrolling } = props;
23
+ const { size = "md", content, forceScrolling, api } = props;
24
24
  const isFixedHeight = typeof size !== "string";
25
25
  const ref = (0, react_1.useRef)(null);
26
26
  const { modalBodyDiv, modalFooterDiv, modalHeaderDiv, drawerContentStack } = (0, BeamContext_1.useBeamContext)();
@@ -37,13 +37,16 @@ function Modal(props) {
37
37
  }, ref);
38
38
  const { modalProps } = (0, react_aria_1.useModal)();
39
39
  const { dialogProps, titleProps } = (0, react_aria_1.useDialog)({ role: "dialog" }, ref);
40
- const [width, height] = getSize(size);
40
+ const [[width, height], setSize] = (0, react_1.useState)(getSize(size));
41
41
  const contentRef = (0, react_1.useRef)(null);
42
42
  const modalBodyRef = (0, react_1.useRef)(null);
43
43
  const modalFooterRef = (0, react_1.useRef)(null);
44
44
  const modalHeaderRef = (0, react_1.useRef)(null);
45
45
  const testId = (0, utils_1.useTestIds)({}, testIdPrefix);
46
46
  (0, react_aria_1.usePreventScroll)();
47
+ if (api) {
48
+ api.current = { setSize: (size = "md") => setSize(getSize(size)) };
49
+ }
47
50
  const [hasScroll, setHasScroll] = (0, react_1.useState)(forceScrolling !== null && forceScrolling !== void 0 ? forceScrolling : false);
48
51
  (0, resize_observer_1.default)(contentRef, ({ target }) => {
49
52
  if (forceScrolling === undefined && !isFixedHeight) {
@@ -4,5 +4,6 @@ export interface UseModalHook {
4
4
  openModal: (props: ModalProps) => void;
5
5
  closeModal: Callback;
6
6
  addCanClose: (canClose: CheckFn) => void;
7
+ setSize: (size: ModalProps["size"]) => void;
7
8
  }
8
9
  export declare function useModal(): UseModalHook;
@@ -7,6 +7,7 @@ const utils_1 = require("../../utils");
7
7
  function useModal() {
8
8
  const { modalState, modalCanCloseChecks } = (0, BeamContext_1.useBeamContext)();
9
9
  const lastCanClose = (0, react_1.useRef)();
10
+ const api = (0, react_1.useRef)();
10
11
  (0, react_1.useEffect)(() => {
11
12
  return () => {
12
13
  modalCanCloseChecks.current = modalCanCloseChecks.current.filter((c) => c !== lastCanClose.current);
@@ -16,7 +17,7 @@ function useModal() {
16
17
  openModal(props) {
17
18
  // TODO Check already open?
18
19
  // TODO Check can leave?
19
- modalState.current = props;
20
+ modalState.current = { ...props, api };
20
21
  },
21
22
  closeModal() {
22
23
  var _a;
@@ -38,6 +39,12 @@ function useModal() {
38
39
  ];
39
40
  lastCanClose.current = canClose;
40
41
  },
42
+ setSize(size) {
43
+ var _a;
44
+ if (modalState.current && ((_a = modalState.current.api) === null || _a === void 0 ? void 0 : _a.current)) {
45
+ modalState.current.api.current.setSize(size);
46
+ }
47
+ },
41
48
  }), [modalState, modalCanCloseChecks]);
42
49
  }
43
50
  exports.useModal = useModal;
@@ -21,7 +21,14 @@ export interface GridStyle {
21
21
  firstNonHeaderRowCss?: Properties;
22
22
  /** Applied to all cell divs (via a selector off the base div). */
23
23
  cellCss?: Properties;
24
- /** Applied to the header (really first) row div. */
24
+ /**
25
+ * Applied to the header row divs.
26
+ *
27
+ * NOTE `as=virtual`: When using a virtual table with the goal of adding space
28
+ * between the header and the first row use `firstNonHeaderRowCss` with a
29
+ * margin-top instead. Using `headerCellCss` will not work since the header
30
+ * rows are wrapper with Chrome rows.
31
+ */
25
32
  headerCellCss?: Properties;
26
33
  /** Applied to the first cell of all rows, i.e. for table-wide padding or left-side borders. */
27
34
  firstCellCss?: Properties;
@@ -112,6 +112,7 @@ function GridTable(props) {
112
112
  // changes, and so by not passing the sortProps, it means the data rows' React.memo will still cache them.
113
113
  const sortProps = row.kind === "header" ? { sorting, sortState, setSortKey } : { sorting };
114
114
  const RowComponent = observeRows ? ObservedGridRow : MemoizedGridRow;
115
+ const openCards = row.kind === "header" ? headerCards : rowCards;
115
116
  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({}, {
116
117
  as,
117
118
  columns,
@@ -120,16 +121,18 @@ function GridTable(props) {
120
121
  rowStyles,
121
122
  stickyHeader,
122
123
  stickyOffset,
123
- openCards: nestedCards ? nestedCards.currentOpenCards() : undefined,
124
+ openCards: openCards ? openCards.currentOpenCards() : undefined,
124
125
  columnSizes,
125
126
  ...sortProps,
126
- }), `${row.kind}-${row.id}`) }), `${row.kind}-${row.id}`));
127
+ }), void 0) }), `${row.kind}-${row.id}`));
127
128
  }
128
129
  // Split out the header rows from the data rows so that we can put an `infoMessage` in between them (if needed).
129
130
  const headerRows = [];
130
131
  const filteredRows = [];
131
132
  // Misc state to track our nested card-ification, i.e. interleaved actual rows + chrome rows
132
- const nestedCards = !!style.nestedCards && new nestedCards_1.NestedCards(columns, filteredRows, style);
133
+ // for the header and row (non-header rows) cards.
134
+ const headerCards = !!style.nestedCards && new nestedCards_1.NestedCards(columns, headerRows, style.nestedCards);
135
+ const rowCards = !!style.nestedCards && new nestedCards_1.NestedCards(columns, filteredRows, style.nestedCards);
133
136
  // Depth-first to filter
134
137
  function visit(row) {
135
138
  var _a;
@@ -139,32 +142,38 @@ function GridTable(props) {
139
142
  // Even if we don't pass the filter, one of our children might, so we continue on after this check
140
143
  let isCard = false;
141
144
  if (matches) {
142
- isCard = nestedCards && nestedCards.maybeOpenCard(row);
145
+ isCard = rowCards && rowCards.maybeOpenCard(row);
143
146
  filteredRows.push([row, makeRowComponent(row)]);
144
147
  }
145
148
  const isCollapsed = collapsedIds.includes(row.id);
146
149
  if (!isCollapsed && !!((_a = row.children) === null || _a === void 0 ? void 0 : _a.length)) {
147
- nestedCards && matches && nestedCards.addSpacer();
150
+ rowCards && matches && rowCards.addSpacer();
148
151
  visitRows(row.children, isCard);
149
152
  }
150
- isCard && nestedCards && nestedCards.closeCard();
153
+ isCard && rowCards && rowCards.closeCard();
151
154
  }
152
155
  function visitRows(rows, addSpacer) {
153
156
  const length = rows.length;
154
157
  rows.forEach((row, i) => {
158
+ var _a;
155
159
  if (row.kind === "header") {
156
- nestedCards && nestedCards.maybeOpenCard(row);
160
+ // Flag to determine if the header has nested card styles.
161
+ // This will determine if we should include opening and closing
162
+ // Chrome rows.
163
+ const isHeaderNested = !!((_a = style.nestedCards) === null || _a === void 0 ? void 0 : _a.kinds["header"]);
164
+ isHeaderNested && headerCards && headerCards.maybeOpenCard(row);
157
165
  headerRows.push([row, makeRowComponent(row)]);
158
- nestedCards && nestedCards.closeCard();
166
+ isHeaderNested && headerCards && headerCards.closeCard();
167
+ isHeaderNested && headerCards && headerCards.done();
159
168
  return;
160
169
  }
161
170
  visit(row);
162
- addSpacer && nestedCards && i !== length - 1 && nestedCards.addSpacer();
171
+ addSpacer && rowCards && i !== length - 1 && rowCards.addSpacer();
163
172
  });
164
173
  }
165
174
  // If nestedCards is set, we assume the top-level kind is a card, and so should add spacers between them
166
- visitRows(maybeSorted, !!nestedCards);
167
- nestedCards && nestedCards.done();
175
+ visitRows(maybeSorted, !!rowCards);
176
+ rowCards && rowCards.done();
168
177
  return [headerRows, filteredRows];
169
178
  }, [
170
179
  as,
@@ -214,15 +223,46 @@ const renders = {
214
223
  virtual: renderVirtual,
215
224
  };
216
225
  /** Renders as a CSS Grid, which is the default / most well-supported rendered. */
217
- function renderCssGrid(style, id, columns, headerRows, filteredRows, firstRowMessage, stickyHeader, firstLastColumnWidth, xss, virtuosoRef) {
226
+ function renderCssGrid(style, id, columns, headerRows, filteredRows, firstRowMessage, _stickyHeader, firstLastColumnWidth, xss, _virtuosoRef) {
227
+ var _a;
228
+ // We must determine if the header is using nested card styles to account for
229
+ // the opening and closing Chrome rows.
230
+ const isNestedCardStyleHeader = !!((_a = style.nestedCards) === null || _a === void 0 ? void 0 : _a.kinds["header"]);
231
+ /*
232
+ Determine at what CSS selector index is the first non header row. Note that
233
+ CSS selectors are not zero-based, unlike JavaScript, so we must add 1 to
234
+ what we'd normally expect.
235
+
236
+ Ex: When we don't have nestedCard styled headers, then we don't have opening
237
+ and closing Chrome rows. Therefore, the first non-header row is the second
238
+ row and since CSS selectors are not zero-based, we are aiming for a value
239
+ of 2 (1 + 1).
240
+
241
+ Ex: When we have nestedCard styled headers, then we have opening and closing
242
+ Chrome rows. Therefore, the first non-header row is the fourth row because:
243
+ - Row 1 is the opening Chrome Row
244
+ - Row 2 is the header
245
+ - Row 3 is the closing Chrome Row
246
+ - Row 4 is the first non-header row
247
+ And since CSS selectors are not zero-based, we are aiming for a value of 4
248
+ (3 + 1).
249
+ */
250
+ const firstNonHeaderRowIndex = (!isNestedCardStyleHeader ? 1 : 3) + 1;
218
251
  return ((0, jsx_runtime_1.jsxs)("div", Object.assign({ css: {
219
252
  ...Css_1.Css.dg.gtc(calcDivGridColumns(columns, firstLastColumnWidth)).$,
220
- // Apply the between-row styling with `div + div > *` so that we don't have to have conditional
221
- // `if !lastRow add border` CSS applied via JS that would mean the row can't be React.memo'd.
222
- // The `div + div` is also the "owl operator", i.e. don't apply to the 1st row.
223
- ...(style.betweenRowsCss ? Css_1.Css.addIn("& > div + div > *", style.betweenRowsCss).$ : {}),
224
- // removes border between header and second row
225
- ...(style.firstNonHeaderRowCss ? Css_1.Css.addIn("& > div:nth-of-type(2) > *", style.firstNonHeaderRowCss).$ : {}),
253
+ /*
254
+ Using n + (firstNonHeaderRowIndex + 1) here to target all rows that
255
+ are after the first non-header row. Since n starts at 0, we can use
256
+ the + operator as an offset.
257
+
258
+ Inspired by: https://stackoverflow.com/a/25005740/2551333
259
+ */
260
+ ...(style.betweenRowsCss
261
+ ? Css_1.Css.addIn(`& > div:nth-of-type(n + ${firstNonHeaderRowIndex + 1}) > *`, style.betweenRowsCss).$
262
+ : {}),
263
+ ...(style.firstNonHeaderRowCss
264
+ ? Css_1.Css.addIn(`& > div:nth-of-type(${firstNonHeaderRowIndex}) > *`, style.firstNonHeaderRowCss).$
265
+ : {}),
226
266
  ...style.rootCss,
227
267
  ...xss,
228
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));
@@ -259,6 +299,7 @@ function renderTable(style, id, columns, headerRows, filteredRows, firstRowMessa
259
299
  * [3]: https://github.com/tannerlinsley/react-virtual/issues/108
260
300
  */
261
301
  function renderVirtual(style, id, columns, headerRows, filteredRows, firstRowMessage, stickyHeader, firstLastColumnWidth, xss, virtuosoRef) {
302
+ // eslint-disable-next-line react-hooks/rules-of-hooks
262
303
  const { footerStyle, listStyle } = (0, react_1.useMemo)(() => {
263
304
  var _a;
264
305
  const { paddingBottom, ...otherRootStyles } = (_a = style.rootCss) !== null && _a !== void 0 ? _a : {};
@@ -279,35 +320,56 @@ function renderVirtual(style, id, columns, headerRows, filteredRows, firstRowMes
279
320
  // so instead drill into the 1st real content cell.
280
321
  return maybeContentsDiv.firstElementChild.getBoundingClientRect().height;
281
322
  }, itemContent: (index) => {
282
- // We keep header and filter rows separate, but react-virtuoso is a flat list,
283
- // so we pick the right header / first row message / actual row.
284
- let i = index;
285
- if (i < headerRows.length) {
286
- return headerRows[i][1];
323
+ // Since we have two arrays of rows: `headerRows` and `filteredRow` we
324
+ // must determine which one to render.
325
+ // Determine if we need to render a header row
326
+ if (index < headerRows.length) {
327
+ return headerRows[index][1];
287
328
  }
288
- i -= headerRows.length;
329
+ // Reset index
330
+ index -= headerRows.length;
331
+ // Show firstRowMessage as the first `filteredRow`
289
332
  if (firstRowMessage) {
290
- if (i === 0) {
333
+ if (index === 0) {
291
334
  return ((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));
292
335
  }
293
- i -= 1;
336
+ // Shift index -1 when there is a firstRowMessage to not skip the
337
+ // first `filteredRow`
338
+ index--;
294
339
  }
295
- return filteredRows[i][1];
340
+ // Lastly render `filteredRow`
341
+ return filteredRows[index][1];
296
342
  }, totalCount: (headerRows.length || 0) + (firstRowMessage ? 1 : 0) + (filteredRows.length || 0) }, void 0));
297
343
  }
298
344
  /**
299
- * Customizes the `List` element that react-virtuoso renders, to have our css grid logic.
345
+ * A table might render two of these components to represent two virtual lists.
346
+ * This generally happens when `topItemCount` prop is used and React-Virtuoso
347
+ * creates to Virtual lists where the first represents, generally, the header
348
+ * rows and the second represents the non-header rows (list rows).
300
349
  *
301
- * We wrap this in memoizeOne so that React.createElement sees a consistent/stable component
302
- * identity, even though technically we have a different "component" per the given set of props
303
- * (solely to capture as params that we can't pass through react-virtuoso's API as props).
350
+ * The main goal of this custom component is to:
351
+ * - Customize the list wrapper to our css grid logic styles
352
+ *
353
+ * We wrap this in memoizeOne so that React.createElement sees a
354
+ * consistent/stable component identity, even though technically we have a
355
+ * different "component" per the given set of props (solely to capture as
356
+ * params that we can't pass through react-virtuoso's API as props).
304
357
  */
305
- const VirtualRoot = (0, memoize_one_1.default)((gs, columns, id, firstLastColumnWidth, xss) => {
358
+ const VirtualRoot = (0, memoize_one_1.default)((gs, _columns, id, _firstLastColumnWidth, xss) => {
306
359
  return react_1.default.forwardRef(function VirtualRoot({ style, children }, ref) {
307
- // This re-renders each time we have new children in the view port
308
- return ((0, jsx_runtime_1.jsx)("div", Object.assign({ ref: ref, style: style, css: {
360
+ // This VirtualRoot list represent the header when no styles are given. The
361
+ // table list generally has styles to scroll the page for windowing.
362
+ const isHeader = Object.keys(style || {}).length === 0;
363
+ // 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: {
309
365
  // Add an extra `> div` due to Item + itemContent both having divs
310
- ...Css_1.Css.addIn("& > div + div > div", gs.betweenRowsCss || {}).$,
366
+ ...Css_1.Css.addIn("& > div + div > div > *", gs.betweenRowsCss || {}).$,
367
+ // Table list styles only
368
+ ...(isHeader
369
+ ? {}
370
+ : {
371
+ ...Css_1.Css.addIn("& > div:first-of-type > *", gs.firstNonHeaderRowCss).$,
372
+ }),
311
373
  ...gs.rootCss,
312
374
  ...xss,
313
375
  }, "data-testid": id }, { children: children }), void 0));
@@ -425,7 +487,8 @@ function GridRow(props) {
425
487
  // root-element > row-element > cell-elements, so that we can have a hook for
426
488
  // hovers and styling. In theory this would change with subgrids.
427
489
  // Only enable when using div as elements
428
- ...(as === "table" ? {} : as === "virtual" ? Css_1.Css.df.$ : Css_1.Css.display("contents").$),
490
+ // 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").$),
429
492
  ...(((rowStyle === null || rowStyle === void 0 ? void 0 : rowStyle.rowLink) || (rowStyle === null || rowStyle === void 0 ? void 0 : rowStyle.onClick)) &&
430
493
  style.rowHoverColor && {
431
494
  // Even though backgroundColor is set on the cellCss (due to display: content), the hover target is the row.
@@ -1,5 +1,7 @@
1
1
  import { ReactElement } from "react";
2
- import { GridColumn, GridDataRow, GridStyle, Kinded, NestedCardsStyle, NestedCardStyle, NestedCardStyleByKind, RowTuple } from "./GridTable";
2
+ import { GridColumn, GridDataRow, Kinded, NestedCardsStyle, NestedCardStyle, NestedCardStyleByKind, RowTuple } from "./GridTable";
3
+ declare type Chrome = () => JSX.Element;
4
+ declare type ChromeBuffer = Chrome[];
3
5
  /**
4
6
  * A helper class to create our nested card DOM shenanigans.
5
7
  *
@@ -12,22 +14,47 @@ import { GridColumn, GridDataRow, GridStyle, Kinded, NestedCardsStyle, NestedCar
12
14
  * a content row itself, the nested padding is handled separately by the
13
15
  * GridRow component.
14
16
  */
15
- declare type Chrome = () => JSX.Element;
16
- declare type ChromeBuffer = Chrome[];
17
17
  export declare class NestedCards {
18
- private columns;
19
- private filteredRows;
20
- private style;
18
+ private readonly columns;
19
+ private readonly rows;
20
+ private readonly styles;
21
21
  private readonly openCards;
22
22
  private readonly chromeBuffer;
23
- private readonly styles;
24
- constructor(columns: GridColumn<any>[], filteredRows: RowTuple<any>[], style: GridStyle);
23
+ private chromeRowIndex;
24
+ constructor(columns: GridColumn<any>[], rows: RowTuple<any>[], styles: NestedCardsStyle);
25
+ /**
26
+ * Maybe add an opening Chrome row to the given `row` if its a nested card.
27
+ *
28
+ * @param row The row which will be opened. The open Chrome row will appear
29
+ * above this row.
30
+ */
25
31
  maybeOpenCard(row: GridDataRow<any>): boolean;
26
32
  closeCard(): void;
27
33
  addSpacer(): void;
34
+ /**
35
+ * Close the remaining open rows with a close Chrome row.
36
+ *
37
+ * @param row The row that is completing/closing nested open Chrome rows.
38
+ */
28
39
  done(): void;
29
40
  /** Return a stable copy of the cards, so it won't change as we keep going. */
30
41
  currentOpenCards(): string;
42
+ /**
43
+ * Takes the current Chrome row buffer of close row(s), spacers, and open row,
44
+ * and creates a single chrome DOM row.
45
+ *
46
+ * This allows a minimal amount of DOM overhead, insofar as to the css-grid or
47
+ * react-virtuoso we only add 1 extra DOM node between each row of content to
48
+ * achieve our nested card look & feel.
49
+ *
50
+ * i.e.:
51
+ * - chrome row (open)
52
+ * - card1 content row
53
+ * - chrome row (card2 open)
54
+ * - nested card2 content row
55
+ * - chrome row (card2 close, card1 close)
56
+ */
57
+ maybeCreateChromeRow(): void;
31
58
  }
32
59
  /**
33
60
  * Draws the rounded corners (either open or close) for a new card.
@@ -55,23 +82,10 @@ export declare function maybeAddCardPadding(openCards: NestedCardStyle[], column
55
82
  * have any open cards, but still want a space between top-level cards.
56
83
  */
57
84
  export declare function makeSpacer(height: number, openCards: string[], styles: NestedCardsStyle): Chrome;
58
- /**
59
- * Takes the current buffer of close row(s), spacers, and open row, and creates a single chrome DOM row.
60
- *
61
- * This allows a minimal amount of DOM overhead, insofar as to the css-grid or react-virtuoso we only
62
- * 1 extra DOM node between each row of content to achieve our nested card look & feel, i.e.:
63
- *
64
- * - chrome row (open)
65
- * - card1 content row
66
- * - chrome row (card2 open)
67
- * - nested card2 content row
68
- * - chrome row (card2 close, card1 close)
69
- */
70
- export declare function maybeCreateChromeRow(columns: GridColumn<any>[], filteredRows: RowTuple<any>[], chromeBuffer: ChromeBuffer): void;
71
85
  interface ChromeRowProps {
72
86
  chromeBuffer: ChromeBuffer;
73
87
  columns: number;
74
88
  }
75
89
  export declare function ChromeRow({ chromeBuffer, columns }: ChromeRowProps): import("@emotion/react/jsx-runtime").JSX.Element;
76
- export declare function dropChromeRows<R extends Kinded>(filteredRows: RowTuple<R>[]): [GridDataRow<R>, ReactElement][];
90
+ export declare function dropChromeRows<R extends Kinded>(rows: RowTuple<R>[]): [GridDataRow<R>, ReactElement][];
77
91
  export {};
@@ -1,20 +1,40 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.dropChromeRows = exports.ChromeRow = exports.maybeCreateChromeRow = exports.makeSpacer = exports.maybeAddCardPadding = exports.makeOpenOrCloseCard = exports.NestedCards = void 0;
3
+ exports.dropChromeRows = exports.ChromeRow = exports.makeSpacer = exports.maybeAddCardPadding = exports.makeOpenOrCloseCard = exports.NestedCards = void 0;
4
4
  const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
5
5
  const react_1 = require("react");
6
6
  const Css_1 = require("../../Css");
7
+ /**
8
+ * A helper class to create our nested card DOM shenanigans.
9
+ *
10
+ * This acts as a one-off visitor that accepts "begin row", "between row",
11
+ * "end row" calls from GridTable while its translating the user's nested
12
+ * GridDataRows into a flat list of RowTuples, and interjects fake/chrome
13
+ * rows into `filteredRows` as necessary.
14
+ *
15
+ * Note that this class only handles *between row* chrome and that within
16
+ * a content row itself, the nested padding is handled separately by the
17
+ * GridRow component.
18
+ */
7
19
  class NestedCards {
8
- constructor(columns, filteredRows, style) {
20
+ constructor(columns,
21
+ // An array of rows which we will add open Chrome rows too to create the table.
22
+ rows, styles) {
9
23
  this.columns = columns;
10
- this.filteredRows = filteredRows;
11
- this.style = style;
24
+ this.rows = rows;
25
+ this.styles = styles;
12
26
  // A stack of the current cards we're showing
13
27
  this.openCards = [];
14
28
  // A buffer of the open/close/spacer rows we need between each content row.
15
29
  this.chromeBuffer = [];
16
- this.styles = style.nestedCards;
30
+ this.chromeRowIndex = 0;
17
31
  }
32
+ /**
33
+ * Maybe add an opening Chrome row to the given `row` if its a nested card.
34
+ *
35
+ * @param row The row which will be opened. The open Chrome row will appear
36
+ * above this row.
37
+ */
18
38
  maybeOpenCard(row) {
19
39
  const card = this.styles.kinds[row.kind];
20
40
  // If this kind doesn't have a card defined, don't put it on the card stack
@@ -23,7 +43,7 @@ class NestedCards {
23
43
  this.chromeBuffer.push(makeOpenOrCloseCard(this.openCards, this.styles.kinds, "open"));
24
44
  }
25
45
  // But always close previous cards if needed
26
- maybeCreateChromeRow(this.columns, this.filteredRows, this.chromeBuffer);
46
+ this.maybeCreateChromeRow();
27
47
  return !!card;
28
48
  }
29
49
  closeCard() {
@@ -33,13 +53,43 @@ class NestedCards {
33
53
  addSpacer() {
34
54
  this.chromeBuffer.push(makeSpacer(this.styles.spacerPx, this.openCards, this.styles));
35
55
  }
56
+ /**
57
+ * Close the remaining open rows with a close Chrome row.
58
+ *
59
+ * @param row The row that is completing/closing nested open Chrome rows.
60
+ */
36
61
  done() {
37
- maybeCreateChromeRow(this.columns, this.filteredRows, this.chromeBuffer);
62
+ this.maybeCreateChromeRow();
38
63
  }
39
64
  /** Return a stable copy of the cards, so it won't change as we keep going. */
40
65
  currentOpenCards() {
41
66
  return this.openCards.join(":");
42
67
  }
68
+ /**
69
+ * Takes the current Chrome row buffer of close row(s), spacers, and open row,
70
+ * and creates a single chrome DOM row.
71
+ *
72
+ * This allows a minimal amount of DOM overhead, insofar as to the css-grid or
73
+ * react-virtuoso we only add 1 extra DOM node between each row of content to
74
+ * achieve our nested card look & feel.
75
+ *
76
+ * i.e.:
77
+ * - chrome row (open)
78
+ * - card1 content row
79
+ * - chrome row (card2 open)
80
+ * - nested card2 content row
81
+ * - chrome row (card2 close, card1 close)
82
+ */
83
+ maybeCreateChromeRow() {
84
+ if (this.chromeBuffer.length > 0) {
85
+ this.rows.push([
86
+ undefined,
87
+ (0, jsx_runtime_1.jsx)(ChromeRow, { chromeBuffer: [...this.chromeBuffer], columns: this.columns.length }, this.chromeRowIndex++),
88
+ ]);
89
+ // clear the Chrome buffer
90
+ this.chromeBuffer.splice(0, this.chromeBuffer.length);
91
+ }
92
+ }
43
93
  }
44
94
  exports.NestedCards = NestedCards;
45
95
  /**
@@ -125,33 +175,13 @@ function makeSpacer(height, openCards, styles) {
125
175
  };
126
176
  }
127
177
  exports.makeSpacer = makeSpacer;
128
- /**
129
- * Takes the current buffer of close row(s), spacers, and open row, and creates a single chrome DOM row.
130
- *
131
- * This allows a minimal amount of DOM overhead, insofar as to the css-grid or react-virtuoso we only
132
- * 1 extra DOM node between each row of content to achieve our nested card look & feel, i.e.:
133
- *
134
- * - chrome row (open)
135
- * - card1 content row
136
- * - chrome row (card2 open)
137
- * - nested card2 content row
138
- * - chrome row (card2 close, card1 close)
139
- */
140
- function maybeCreateChromeRow(columns, filteredRows, chromeBuffer) {
141
- if (chromeBuffer.length > 0) {
142
- filteredRows.push([undefined, (0, jsx_runtime_1.jsx)(ChromeRow, { chromeBuffer: [...chromeBuffer], columns: columns.length }, void 0)]);
143
- // clear the buffer
144
- chromeBuffer.splice(0, chromeBuffer.length);
145
- }
146
- }
147
- exports.maybeCreateChromeRow = maybeCreateChromeRow;
148
178
  function ChromeRow({ chromeBuffer, columns }) {
149
179
  return (
150
180
  // We add 2 to account for our dedicated open/close columns
151
181
  (0, jsx_runtime_1.jsx)("div", Object.assign({ css: Css_1.Css.gc(`span ${columns + 2}`).$, "data-chrome": "true" }, { children: chromeBuffer.map((c, i) => ((0, jsx_runtime_1.jsx)(react_1.Fragment, { children: c() }, i))) }), void 0));
152
182
  }
153
183
  exports.ChromeRow = ChromeRow;
154
- function dropChromeRows(filteredRows) {
155
- return filteredRows.filter(([r]) => !!r);
184
+ function dropChromeRows(rows) {
185
+ return rows.filter(([r]) => !!r);
156
186
  }
157
187
  exports.dropChromeRows = dropChromeRows;
@@ -12,7 +12,7 @@ exports.defaultStyle = {
12
12
  lastCellCss: Css_1.Css.$,
13
13
  indentOneCss: Css_1.Css.pl4.$,
14
14
  indentTwoCss: Css_1.Css.pl7.$,
15
- headerCellCss: Css_1.Css.asfe.nowrap.py1.bgGray100.aife.$,
15
+ headerCellCss: Css_1.Css.nowrap.py1.bgGray100.aife.$,
16
16
  firstRowMessageCss: Css_1.Css.px1.py2.$,
17
17
  rowHoverColor: Css_1.Palette.Gray200,
18
18
  };
@@ -4,40 +4,27 @@ exports.CompoundField = void 0;
4
4
  const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
5
5
  const react_1 = require("react");
6
6
  const Css_1 = require("../../Css");
7
- const utils_1 = require("../../utils");
8
7
  /** Internal component to help create compound fields */
9
8
  function CompoundField({ children }) {
10
9
  if ((children === null || children === void 0 ? void 0 : children.length) !== 2) {
11
10
  throw global.Error("CompoundField requires two children components");
12
11
  }
13
- const [focusedEl, setFocusedEl] = (0, react_1.useState)();
14
12
  const commonStyles = Css_1.Css.df.aic.fs1.maxwPx(550).bt.bb.bGray300.$;
15
13
  const internalProps = { compound: true };
16
- return ((0, jsx_runtime_1.jsxs)("div", Object.assign({ css: Css_1.Css.df.$ }, { children: [(0, jsx_runtime_1.jsx)("div", Object.assign({ css: {
14
+ return ((0, jsx_runtime_1.jsxs)("div", Object.assign({ css: {
15
+ ...Css_1.Css.df.$,
16
+ "&:focus-within > div:nth-of-type(2)": Css_1.Css.bgLightBlue700.$, // Separation line when inputs are focused
17
+ } }, { children: [(0, jsx_runtime_1.jsx)("div", Object.assign({ css: {
17
18
  ...commonStyles,
18
- ...Css_1.Css.bl.borderRadius("4px 0 0 4px").if(focusedEl === "left").bLightBlue700.$,
19
+ ...Css_1.Css.bl.borderRadius("4px 0 0 4px").$,
20
+ "&:focus-within": Css_1.Css.bLightBlue700.$,
19
21
  } }, { children: (0, react_1.cloneElement)(children[0], {
20
- onFocus: () => {
21
- (0, utils_1.maybeCall)(children[0].props.onFocus);
22
- setFocusedEl("left");
23
- },
24
- onBlur: () => {
25
- (0, utils_1.maybeCall)(children[0].props.onBlur);
26
- setFocusedEl(undefined);
27
- },
28
22
  internalProps,
29
- }) }), void 0), (0, jsx_runtime_1.jsx)("div", { css: Css_1.Css.wPx(1).flexNone.bgGray300.if(focusedEl !== undefined).bgLightBlue700.$ }, void 0), (0, jsx_runtime_1.jsx)("div", Object.assign({ css: {
23
+ }) }), void 0), (0, jsx_runtime_1.jsx)("div", { css: Css_1.Css.wPx(1).flexNone.bgGray300.$ }, void 0), (0, jsx_runtime_1.jsx)("div", Object.assign({ css: {
30
24
  ...commonStyles,
31
- ...Css_1.Css.fg1.br.borderRadius("0 4px 4px 0").if(focusedEl === "right").bLightBlue700.$,
25
+ ...Css_1.Css.fg1.br.borderRadius("0 4px 4px 0").$,
26
+ "&:focus-within": Css_1.Css.bLightBlue700.$,
32
27
  } }, { children: (0, react_1.cloneElement)(children[1], {
33
- onFocus: () => {
34
- (0, utils_1.maybeCall)(children[1].props.onFocus);
35
- setFocusedEl("right");
36
- },
37
- onBlur: () => {
38
- (0, utils_1.maybeCall)(children[1].props.onBlur);
39
- setFocusedEl(undefined);
40
- },
41
28
  internalProps,
42
29
  }) }), void 0)] }), void 0));
43
30
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homebound/beam",
3
- "version": "2.98.0",
3
+ "version": "2.99.1",
4
4
  "author": "Homebound",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -48,7 +48,7 @@
48
48
  "react-router": "^5.2.0",
49
49
  "react-router-dom": "^5.2.0",
50
50
  "react-stately": "^3.9.0",
51
- "react-virtuoso": "^2.3.1",
51
+ "react-virtuoso": "^2.4.0",
52
52
  "tinycolor2": "^1.4.2",
53
53
  "tributejs": "^5.1.3",
54
54
  "trix": "^1.3.1",