@homebound/beam 2.304.0 → 2.305.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
- import { GridDataRow, SelectedState } from "../../..";
1
+ import { GridDataRow } from "../components/Row";
2
2
  import { RowStates } from "./RowStates";
3
+ import { SelectedState } from "./TableState";
3
4
  /**
4
5
  * A reactive/observable state of each GridDataRow's current behavior.
5
6
  *
@@ -7,6 +8,7 @@ import { RowStates } from "./RowStates";
7
8
  * that uses parent/children easier to write, i.e. selected-ness and collapsed-ness.
8
9
  */
9
10
  export declare class RowState {
11
+ private states;
10
12
  /** Our row, only ref observed, so we don't crawl into GraphQL fragments. */
11
13
  row: GridDataRow<any>;
12
14
  /** Our children row states, as of the latest `props.rows`, without any filtering applied. */
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RowState = void 0;
4
4
  const mobx_1 = require("mobx");
5
- const src_1 = require("../../..");
5
+ const utils_1 = require("./utils");
6
6
  /**
7
7
  * A reactive/observable state of each GridDataRow's current behavior.
8
8
  *
@@ -15,6 +15,7 @@ class RowState {
15
15
  // isEffectiveMatched = isDirectlyMatched || hasMatchedChildren
16
16
  constructor(states, row) {
17
17
  var _a;
18
+ this.states = states;
18
19
  /** Our children row states, as of the latest `props.rows`, without any filtering applied. */
19
20
  this.children = undefined;
20
21
  /** Whether we match a client-side filter; true if no filter is in place. */
@@ -108,21 +109,19 @@ class RowState {
108
109
  * child of a selected parent row.
109
110
  */
110
111
  select(selected) {
111
- if (this.row.selectable === false)
112
- return;
113
- this.selected = selected;
112
+ if (this.row.selectable !== false) {
113
+ this.selected = selected;
114
+ }
114
115
  // We don't check inferSelectedState here, b/c even if the parent is considered selectable
115
116
  // on its own, we still push down selected-ness to our visible children.
116
- if (this.children) {
117
- for (const child of this.visibleChildren) {
118
- child.select(selected);
119
- }
117
+ for (const child of this.visibleChildren) {
118
+ child.select(selected);
120
119
  }
121
120
  }
122
121
  /** Marks the row as removed from `props.rows`, to potentially become kept. */
123
122
  markRemoved() {
124
123
  // The kept group is never in `props.rows`, so ignore asks to delete it
125
- if (this.row.kind === src_1.KEPT_GROUP)
124
+ if (this.row.kind === utils_1.KEPT_GROUP)
126
125
  return;
127
126
  this.removed = this.selected && this.removed !== "hard" ? "soft" : "hard";
128
127
  }
@@ -136,7 +135,7 @@ class RowState {
136
135
  // - or it has (probably) been server-side filtered
137
136
  return (this.selected &&
138
137
  // Headers, totals, etc., do not need keeping
139
- !src_1.reservedRowKinds.includes(this.row.kind) &&
138
+ !utils_1.reservedRowKinds.includes(this.row.kind) &&
140
139
  !this.isParent &&
141
140
  (!this.isMatched || this.removed === "soft"));
142
141
  }
@@ -144,15 +143,15 @@ class RowState {
144
143
  return this.row.inferSelectedState !== false;
145
144
  }
146
145
  get visibleChildren() {
147
- var _a, _b, _c;
148
- // The keptGroup should treat all of its children as visible, as this makes select/unselect all work.
149
- if (this.row.kind === src_1.KEPT_GROUP)
150
- return (_a = this.children) !== null && _a !== void 0 ? _a : [];
146
+ var _a, _b;
147
+ // The keptGroup is special and its children are the dynamically kept rows
148
+ if (this.row.kind === utils_1.KEPT_GROUP)
149
+ return this.states.keptRows;
151
150
  // Ignore hard-deleted rows, i.e. from `api.deleteRows`; in theory any hard-deleted
152
151
  // rows should be removed from `this.children` anyway, by a change to `props.rows`,
153
152
  // but just in case the user calls _only_ `api.deleteRows`, and expects the row to
154
153
  // go away, go ahead and filter them out here.
155
- return (_c = (_b = this.children) === null || _b === void 0 ? void 0 : _b.filter((c) => c.isMatched === true && c.removed !== "hard")) !== null && _c !== void 0 ? _c : [];
154
+ return (_b = (_a = this.children) === null || _a === void 0 ? void 0 : _a.filter((c) => c.isMatched === true && c.removed !== "hard")) !== null && _b !== void 0 ? _b : [];
156
155
  }
157
156
  /**
158
157
  * Returns whether this row should act like a parent.
@@ -31,5 +31,5 @@ export declare class RowStates {
31
31
  get keptRows(): RowState[];
32
32
  get collapsedRows(): RowState[];
33
33
  /** Create our synthetic "group row" for kept rows, that users never pass in, but we self-inject as needed. */
34
- private creatKeptGroupRow;
34
+ private createKeptGroupRow;
35
35
  }
@@ -14,7 +14,7 @@ class RowStates {
14
14
  this.map = new mobx_1.ObservableMap();
15
15
  this.storage = new RowStorage_1.RowStorage(this);
16
16
  // Pre-create our keptGroupRow for if/when we need it.
17
- this.keptGroupRow = this.creatKeptGroupRow();
17
+ this.keptGroupRow = this.createKeptGroupRow();
18
18
  this.header = undefined;
19
19
  /** The first level of rows, i.e. not the header (or kept group), but the totals + top-level children. */
20
20
  this.topRows = [];
@@ -65,22 +65,16 @@ class RowStates {
65
65
  this.topRows = rows.filter((row) => row !== headerRow).map((row) => addRowAndChildren(row));
66
66
  // And attach them to the header for select-all/etc. to work
67
67
  if (this.header) {
68
- this.header.children = this.topRows.filter((rs) => !utils_1.reservedRowKinds.includes(rs.row.kind));
68
+ this.header.children = [
69
+ // Always add the keptGroupRow, and we'll use keptGroupRow.isMatched=true/false to keep it
70
+ // from messing up "header is all selected" if its hidden/when there are no kept rows.
71
+ this.keptGroupRow,
72
+ ...this.topRows.filter((rs) => !utils_1.reservedRowKinds.includes(rs.row.kind)),
73
+ ];
69
74
  }
70
75
  // Then mark any remaining as removed
71
76
  for (const state of existing)
72
77
  state.markRemoved();
73
- const keptRows = this.keptRows;
74
- if (keptRows.length > 0) {
75
- // Stitch the current keptRows into the placeholder keptGroupRow
76
- this.keptGroupRow.children = keptRows;
77
- this.keptGroupRow.row.children = keptRows.map((rs) => rs.row);
78
- // And then stitch the keptGroupRow itself into the root header, so that the kept rows
79
- // are treated as just another child for the header's select/unselect all to work.
80
- if (this.header) {
81
- this.header.children.unshift(this.keptGroupRow);
82
- }
83
- }
84
78
  // After the first load of real data, we detach collapse state, to respect
85
79
  // any incoming initCollapsed.
86
80
  if (this.topRows.some((rs) => !utils_1.reservedRowKinds.includes(rs.row.kind))) {
@@ -134,6 +128,11 @@ class RowStates {
134
128
  rs.isMatched = ids.includes(rs.row.id);
135
129
  }
136
130
  }
131
+ // We cheat a little and pretend the "kept group matches the filter" not based on it itself
132
+ // literally matching the filter, or its children matching the filter (which is typically
133
+ // how the filter logic works), but if it just has any child rows at all (which actually means
134
+ // its children did _not_ match the filter, but are kept).
135
+ this.keptGroupRow.isMatched = this.keptRows.length > 0;
137
136
  }
138
137
  /** Returns kept rows, i.e. those that were user-selected but then client-side or server-side filtered. */
139
138
  get keptRows() {
@@ -143,10 +142,20 @@ class RowStates {
143
142
  return this.allStates.filter((rs) => rs.collapsed);
144
143
  }
145
144
  /** Create our synthetic "group row" for kept rows, that users never pass in, but we self-inject as needed. */
146
- creatKeptGroupRow() {
145
+ createKeptGroupRow() {
147
146
  // The "group row" for selected rows that are hidden by filters and add the children
148
- const keptGroupRow = { id: utils_1.KEPT_GROUP, kind: utils_1.KEPT_GROUP, initCollapsed: true, data: undefined };
149
- return new RowState_1.RowState(this, keptGroupRow);
147
+ const keptGroupRow = {
148
+ id: utils_1.KEPT_GROUP,
149
+ kind: utils_1.KEPT_GROUP,
150
+ initCollapsed: true,
151
+ selectable: false,
152
+ data: undefined,
153
+ children: [],
154
+ };
155
+ const rs = new RowState_1.RowState(this, keptGroupRow);
156
+ // Make the RowState behave like a parent, even though we calc its visibleChildren.
157
+ rs.children = [];
158
+ return rs;
150
159
  }
151
160
  }
152
161
  exports.RowStates = RowStates;
@@ -45,12 +45,8 @@ class TableState {
45
45
  // Make ourselves an observable so that mobx will do caching of .collapseIds so
46
46
  // that it'll be a stable identity for GridTable to useMemo against.
47
47
  (0, mobx_1.makeAutoObservable)(this, {
48
- // We only shallow observe rows so that:
49
- // a) we don't deeply/needlessly proxy-ize a large Apollo fragment cache, but
50
- // b) if rows changes, we re-run computeds like getSelectedRows that may need to see the
51
- // updated _contents_ of a given row, even if our other selected/matched row states don't change.
52
- // (as any b/c rows is private, so the mapped type doesn't see it)
53
- rows: mobx_1.observable.shallow,
48
+ // We use `ref`s so that observables can watch the immutable data change w/o deeply proxy-ifying Apollo fragments
49
+ rows: mobx_1.observable.ref,
54
50
  columns: mobx_1.observable.ref,
55
51
  });
56
52
  // If the kept rows went from empty to not empty, then introduce the SELECTED_GROUP row as collapsed
@@ -119,10 +115,6 @@ class TableState {
119
115
  }
120
116
  // Updates the list of rows and regenerates the collapsedRows property if needed.
121
117
  setRows(rows) {
122
- // Note that because of using `rows: observable.shallow` above, this is always
123
- // false, and this logic runs on every render. We can eventually fix this, but it
124
- // is convenient b/c it puts no-longer-kept rows back into the right spot in their
125
- // parents.
126
118
  if (rows !== this.rows) {
127
119
  this.rowStates.setRows(rows);
128
120
  this.rows = rows;
@@ -26,8 +26,6 @@ function DateFieldBase(props) {
26
26
  const overlayRef = (0, react_1.useRef)(null);
27
27
  // Local focus ref used to avoid updating WIP values
28
28
  const isFocused = (0, react_1.useRef)(false);
29
- // Ref helper to identify when focus is returned to the TextField due to the DatePicker closing.
30
- const closingDatePicker = (0, react_1.useRef)(false);
31
29
  const dateFormat = (0, utils_1.getDateFormat)(format);
32
30
  // The `wipValue` allows the "range" mode to set the value to `undefined`, even if the `onChange` response cannot be undefined.
33
31
  // This makes working within the DateRangePicker much more user-friendly.
@@ -45,10 +43,6 @@ function DateFieldBase(props) {
45
43
  value: inputValue,
46
44
  };
47
45
  const state = (0, react_stately_1.useOverlayTriggerState)({ isOpen: defaultOpen });
48
- const onPickerClose = (0, react_1.useCallback)(() => {
49
- closingDatePicker.current = true;
50
- state.close();
51
- }, [state]);
52
46
  const { labelProps, inputProps } = (0, react_aria_1.useTextField)({
53
47
  ...textFieldProps,
54
48
  // Setting `inputMode` to none. This disables the virtual keyboard from being triggered on touch devices
@@ -56,12 +50,6 @@ function DateFieldBase(props) {
56
50
  onFocus: () => {
57
51
  var _a;
58
52
  isFocused.current = true;
59
- // Open overlay on focus of the input, only if the focus is not triggered due to the overlay being closed.
60
- if (!closingDatePicker.current) {
61
- state.open();
62
- }
63
- // Reset the closingDatePicker ref to false, so that the overlay can be opened again on the next focus event
64
- closingDatePicker.current = false;
65
53
  (0, utils_2.maybeCall)(onFocus);
66
54
  if (wipValue && dateFormat !== utils_1.dateFormats.short) {
67
55
  // When focused, change to use the "short" date format, as it is simpler to update by hand and parse.
@@ -74,7 +62,6 @@ function DateFieldBase(props) {
74
62
  var _a, _b;
75
63
  // Resets the ref variables when the input loses focus.
76
64
  isFocused.current = false;
77
- closingDatePicker.current = false;
78
65
  // If interacting with the overlay or the input, then assume the user is still working within the DatePicker and return early to not trigger onBlur functionality.
79
66
  if ((inputWrapRef.current && inputWrapRef.current.contains(e.relatedTarget)) ||
80
67
  (overlayRef.current && overlayRef.current.contains(e.relatedTarget))) {
@@ -107,18 +94,13 @@ function DateFieldBase(props) {
107
94
  },
108
95
  }, inputRef);
109
96
  const { triggerProps, overlayProps } = (0, react_aria_1.useOverlayTrigger)({ type: "dialog" }, state, buttonRef);
110
- const { buttonProps } = (0, react_aria_1.useButton)({
111
- ...triggerProps,
112
- isDisabled: isDisabled || isReadOnly,
113
- // When pressed then move focus the input, which will select the text and trigger the DatePicker to open
114
- onPress: () => { var _a; return (_a = inputRef === null || inputRef === void 0 ? void 0 : inputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); },
115
- }, buttonRef);
97
+ const { buttonProps } = (0, react_aria_1.useButton)({ ...triggerProps, isDisabled: isDisabled || isReadOnly, onPress: state.open }, buttonRef);
116
98
  const { overlayProps: positionProps } = (0, react_aria_1.useOverlayPosition)({
117
99
  targetRef: inputWrapRef,
118
100
  overlayRef,
119
101
  shouldFlip: true,
120
102
  isOpen: state.isOpen,
121
- onClose: onPickerClose,
103
+ onClose: state.close,
122
104
  placement: "bottom left",
123
105
  shouldUpdatePosition: true,
124
106
  offset: 4,
@@ -164,11 +146,11 @@ function DateFieldBase(props) {
164
146
  setInputValue("");
165
147
  onChange(undefined);
166
148
  } })) }));
167
- const calendarButton = ((0, jsx_runtime_1.jsx)("button", { ref: buttonRef, ...buttonProps, disabled: isDisabled, css: Css_1.Css.if(isDisabled).cursorNotAllowed.$, tabIndex: -1, ...tid.calendarButton, children: (0, jsx_runtime_1.jsx)(components_1.Icon, { icon: "calendar", color: isDisabled ? Css_1.Palette.Gray400 : Css_1.Palette.Gray700 }) }));
149
+ const calendarButton = ((0, jsx_runtime_1.jsx)("button", { ref: buttonRef, ...buttonProps, disabled: isDisabled, css: Css_1.Css.if(isDisabled).cursorNotAllowed.$, ...tid.calendarButton, children: (0, jsx_runtime_1.jsx)(components_1.Icon, { icon: "calendar", color: isDisabled ? Css_1.Palette.Gray400 : Css_1.Palette.Gray700 }) }));
168
150
  const EndFieldButtons = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [isRangeFilterField && clearButton, !hideCalendarIcon && calendarButton] }));
169
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(TextFieldBase_1.TextFieldBase, { ...textFieldProps, ...{ internalProps: { forceFocus: state.isOpen } }, errorMsg: errorMsg, helperText: helperText, required: required, labelProps: labelProps, inputProps: { ...inputProps, size: inputSize }, inputRef: inputRef, inputWrapRef: inputWrapRef, onChange: (v) => {
151
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(TextFieldBase_1.TextFieldBase, { ...textFieldProps, ...{ internalProps: { forceFocus: state.isOpen } }, errorMsg: errorMsg, helperText: helperText, required: required, labelProps: labelProps, inputProps: { ...inputProps, size: inputSize, onClick: state.open }, inputRef: inputRef, inputWrapRef: inputWrapRef, onChange: (v) => {
170
152
  // hide the calendar if the user is manually entering the date
171
- onPickerClose();
153
+ state.close();
172
154
  if (v) {
173
155
  setInputValue(v);
174
156
  // If changing the value directly (vs using the DatePicker), then we always use the short format
@@ -179,16 +161,16 @@ function DateFieldBase(props) {
179
161
  else if (v === undefined) {
180
162
  setInputValue("");
181
163
  }
182
- }, endAdornment: !iconLeft && EndFieldButtons, startAdornment: !hideCalendarIcon && iconLeft && calendarButton, tooltip: (0, components_1.resolveTooltip)(disabled, undefined, readOnly), ...others }), state.isOpen && ((0, jsx_runtime_1.jsx)(internal_1.Popover, { triggerRef: inputWrapRef, popoverRef: overlayRef, positionProps: positionProps, onClose: onPickerClose, isOpen: state.isOpen, children: (0, jsx_runtime_1.jsx)(react_aria_1.FocusScope, { autoFocus: true, restoreFocus: true, children: (0, jsx_runtime_1.jsx)(DatePickerOverlay_1.DatePickerOverlay, { overlayProps: overlayProps, children: isRangeMode ? ((0, jsx_runtime_1.jsx)(internal_1.DateRangePicker, { range: wipValue, disabledDays: disabledDays, onSelect: (dr) => {
164
+ }, endAdornment: !iconLeft && EndFieldButtons, startAdornment: !hideCalendarIcon && iconLeft && calendarButton, tooltip: (0, components_1.resolveTooltip)(disabled, undefined, readOnly), ...others }), state.isOpen && ((0, jsx_runtime_1.jsx)(internal_1.Popover, { triggerRef: inputWrapRef, popoverRef: overlayRef, positionProps: positionProps, onClose: state.close, isOpen: state.isOpen, children: (0, jsx_runtime_1.jsx)(react_aria_1.FocusScope, { autoFocus: true, contain: true, restoreFocus: true, children: (0, jsx_runtime_1.jsx)(DatePickerOverlay_1.DatePickerOverlay, { overlayProps: overlayProps, children: isRangeMode ? ((0, jsx_runtime_1.jsx)(internal_1.DateRangePicker, { range: wipValue, disabledDays: disabledDays, onSelect: (dr) => {
183
165
  var _a;
184
166
  // Note: Do not close date range picker on select to allow the user to select multiple dates at a time
185
167
  setInputValue((_a = (0, utils_1.formatDateRange)(dr, utils_1.dateFormats.short)) !== null && _a !== void 0 ? _a : "");
186
168
  onChange(dr);
187
169
  }, useYearPicker: isRangeFilterField, ...tid.datePicker })) : ((0, jsx_runtime_1.jsx)(internal_1.DatePicker, { value: wipValue, disabledDays: disabledDays, onSelect: (d) => {
188
170
  var _a;
171
+ state.close();
189
172
  setInputValue((_a = (0, utils_1.formatDate)(d, utils_1.dateFormats.short)) !== null && _a !== void 0 ? _a : "");
190
173
  onChange(d);
191
- onPickerClose();
192
174
  }, ...tid.datePicker })) }) }) }))] }));
193
175
  }
194
176
  exports.DateFieldBase = DateFieldBase;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homebound/beam",
3
- "version": "2.304.0",
3
+ "version": "2.305.0",
4
4
  "author": "Homebound",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",