@homebound/beam 2.101.1 → 2.101.5

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.
@@ -8,7 +8,7 @@ export declare type GridTableXss = Xss<Margin>;
8
8
  export declare const ASC: "ASC";
9
9
  export declare const DESC: "DESC";
10
10
  export declare type Direction = "ASC" | "DESC";
11
- export declare const emptyCell: () => ReactNode;
11
+ export declare const emptyCell: GridCellContent;
12
12
  /** Tells GridTable we're running in Jest, which forces as=virtual to be as=div, to work in jsdom. */
13
13
  export declare function setRunningInJest(): void;
14
14
  /** Completely static look & feel, i.e. nothing that is based on row kinds/content. */
@@ -212,7 +212,7 @@ declare type GridRowKind<R extends Kinded, P extends R["kind"]> = DiscriminateUn
212
212
  * column being sorted, in which case we use the GridCellContent.value.
213
213
  */
214
214
  export declare type GridColumn<R extends Kinded, S = {}> = {
215
- [K in R["kind"]]: string | (DiscriminateUnion<R, "kind", K> extends {
215
+ [K in R["kind"]]: string | GridCellContent | (DiscriminateUnion<R, "kind", K> extends {
216
216
  data: infer D;
217
217
  } ? (data: D, row: GridRowKind<R, K>) => ReactNode | GridCellContent : (row: GridRowKind<R, K>) => ReactNode | GridCellContent);
218
218
  } & {
@@ -42,8 +42,7 @@ const tinycolor2_1 = __importDefault(require("tinycolor2"));
42
42
  const _1 = require(".");
43
43
  exports.ASC = "ASC";
44
44
  exports.DESC = "DESC";
45
- const emptyCell = () => (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, {}, void 0);
46
- exports.emptyCell = emptyCell;
45
+ exports.emptyCell = { content: () => (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, {}, void 0), value: "" };
47
46
  let runningInJest = false;
48
47
  /** Tells GridTable we're running in Jest, which forces as=virtual to be as=div, to work in jsdom. */
49
48
  function setRunningInJest() {
@@ -304,7 +303,9 @@ function renderVirtual(style, id, columns, headerRows, filteredRows, firstRowMes
304
303
  const { paddingBottom, ...otherRootStyles } = (_a = style.rootCss) !== null && _a !== void 0 ? _a : {};
305
304
  return { footerStyle: { paddingBottom }, listStyle: { ...style, rootCss: otherRootStyles } };
306
305
  }, [style]);
307
- return ((0, jsx_runtime_1.jsx)(react_virtuoso_1.Virtuoso, { overscan: 5, ref: virtuosoRef, components: {
306
+ return ((0, jsx_runtime_1.jsx)(react_virtuoso_1.Virtuoso, { overscan: 5, ref: virtuosoRef,
307
+ // Add `minWidth: fit-content` to ensure a sticky header and the virtualized table body maintain same width
308
+ style: { minWidth: "fit-content" }, components: {
308
309
  List: VirtualRoot(listStyle, columns, id, firstLastColumnWidth, xss),
309
310
  Footer: () => (0, jsx_runtime_1.jsx)("div", { css: footerStyle }, void 0),
310
311
  },
@@ -19,6 +19,8 @@ export interface NumberFieldProps {
19
19
  /** Styles overrides */
20
20
  xss?: Xss<"textAlign" | "justifyContent">;
21
21
  displayDirection?: boolean;
22
+ numFractionDigits?: number;
23
+ truncate?: boolean;
22
24
  }
23
25
  export declare function NumberField(props: NumberFieldProps): import("@emotion/react/jsx-runtime").JSX.Element;
24
- export declare function parseRawInput(rawInputValue: string | undefined, factor: number, type?: NumberFieldType): number | undefined;
26
+ export declare function formatValue(value: number, factor: number, numFractionDigits: number | undefined): number | undefined;
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parseRawInput = exports.NumberField = void 0;
3
+ exports.formatValue = exports.NumberField = void 0;
4
4
  const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
5
+ const number_1 = require("@internationalized/number");
5
6
  const react_1 = require("react");
6
7
  const react_aria_1 = require("react-aria");
7
8
  const react_stately_1 = require("react-stately");
@@ -12,34 +13,34 @@ function NumberField(props) {
12
13
  // Determine default alignment based on presentation context
13
14
  const { fieldProps } = (0, PresentationContext_1.usePresentationContext)();
14
15
  const alignment = (fieldProps === null || fieldProps === void 0 ? void 0 : fieldProps.numberAlignment) === "right" ? Css_1.Css.tr.jcfe.$ : Css_1.Css.tl.jcfs.$;
15
- const { disabled = false, required, readOnly = false, type, label, onBlur, onFocus, errorMsg, helperText, value, onChange, xss, displayDirection = false, ...otherProps } = props;
16
+ const { disabled = false, required, readOnly = false, type, label, onBlur, onFocus, errorMsg, helperText, value, onChange, xss, displayDirection = false, numFractionDigits, truncate = false, ...otherProps } = props;
16
17
  const factor = type === "percent" || type === "cents" ? 100 : type === "basisPoints" ? 10000 : 1;
17
18
  const signDisplay = displayDirection ? "exceptZero" : "auto";
19
+ const fractionFormatOptions = { [truncate ? "maximumFractionDigits" : "minimumFractionDigits"]: numFractionDigits };
20
+ const { locale } = (0, react_aria_1.useLocale)();
18
21
  // If formatOptions isn't memo'd, a useEffect in useNumberStateField will cause jank,
19
22
  // see: https://github.com/adobe/react-spectrum/issues/1893.
20
23
  const formatOptions = (0, react_1.useMemo)(() => {
21
24
  return type === "percent"
22
- ? { style: "percent", signDisplay }
25
+ ? { style: "percent", signDisplay, ...fractionFormatOptions }
23
26
  : type === "basisPoints"
24
27
  ? { style: "percent", minimumFractionDigits: 2, signDisplay }
25
28
  : type === "cents"
26
29
  ? { style: "currency", currency: "USD", minimumFractionDigits: 2, signDisplay }
27
30
  : type === "days"
28
31
  ? { style: "unit", unit: "day", unitDisplay: "long", maximumFractionDigits: 0, signDisplay }
29
- : undefined;
32
+ : fractionFormatOptions;
30
33
  }, [type]);
34
+ const numberParser = (0, react_1.useMemo)(() => new number_1.NumberParser(locale, formatOptions), [locale, formatOptions]);
31
35
  const valueRef = (0, react_1.useRef)({ wip: false });
32
- const { locale } = (0, react_aria_1.useLocale)();
33
36
  // We can use this for both useNumberFieldState + useNumberField
34
37
  const useProps = {
35
38
  locale,
36
39
  // We want percents && cents to be integers, useNumberFieldState excepts them as decimals
37
40
  value: valueRef.current.wip ? valueRef.current.value : value === undefined ? Number.NaN : value / factor,
38
- // This is called on blur with the final/committed value.
41
+ // // This is called on blur with the final/committed value.
39
42
  onChange: (value) => {
40
- // `value` for percentage style inputs will be in a number format, i.e. if input value is 4%, the `value` param will equal `.04`
41
- // Reverse the integer/decimal conversion
42
- onChange(Number.isNaN(value) ? undefined : factor !== 1 ? Math.round(value * factor) : value);
43
+ onChange(formatValue(value, factor, numFractionDigits));
43
44
  },
44
45
  onFocus: () => {
45
46
  valueRef.current = { wip: true, value: value === undefined ? Number.NaN : value / factor };
@@ -71,20 +72,17 @@ function NumberField(props) {
71
72
  return ((0, jsx_runtime_1.jsx)(TextFieldBase_1.TextFieldBase, Object.assign({ xss: { ...alignment, ...xss }, groupProps: groupProps, labelProps: labelProps, label: label, required: required, inputProps: inputProps,
72
73
  // This is called on each DOM change, to push the latest value into the field
73
74
  onChange: (rawInputValue) => {
74
- const changeValue = parseRawInput(rawInputValue, factor, type);
75
- onChange(changeValue);
75
+ const parsedValue = numberParser.parse(rawInputValue || "");
76
+ onChange(formatValue(parsedValue, factor, numFractionDigits));
76
77
  }, inputRef: inputRef, onBlur: onBlur, onFocus: onFocus, errorMsg: errorMsg, helperText: helperText, readOnly: readOnly }, otherProps), void 0));
77
78
  }
78
79
  exports.NumberField = NumberField;
79
- function parseRawInput(rawInputValue = "", factor, type) {
80
- // If the wip value is invalid, i.e. it's `10b`, don't push that back into the field state
81
- const wip = parseFloat(rawInputValue);
82
- if (isNaN(wip))
83
- return;
84
- // For percentage values we need to initially divide by 100 in order to get their "number value" ("4%" = .04) for the factor multiplier to be accurate.
85
- // For example, if the using basisPoints and the user enters "4.31%", then we would expect the response to be 431 basisPoints. If only basing off the `factor` value, then 4.31 * 10000 = 43100, which would not be correct.
86
- const value = type === "percent" || type === "basisPoints" ? wip / 100 : wip;
87
- // Since the values returned is exactly what is in the field
88
- return factor !== 1 ? Math.round(value * factor) : value;
80
+ function formatValue(value, factor, numFractionDigits) {
81
+ // We treat percents & cents as (mostly) integers, while useNumberField wants decimals, so
82
+ // undo that via `* factor` and `Math.round`, but also keep any specifically-requested `numFractionDigits`,
83
+ // i.e. for `type=percent value=12.34`, `value` will be `0.1234` that we want turn into `12.34`.
84
+ const maybeAdjustForDecimals = numFractionDigits ? Math.pow(10, numFractionDigits) : 1;
85
+ // Reverse the integer/decimal conversion
86
+ return Number.isNaN(value) ? undefined : Math.round(value * factor * maybeAdjustForDecimals) / maybeAdjustForDecimals;
89
87
  }
90
- exports.parseRawInput = parseRawInput;
88
+ exports.formatValue = formatValue;
@@ -33,7 +33,7 @@ function TextFieldBase(props) {
33
33
  const fieldHeight = 40;
34
34
  const compactFieldHeight = 32;
35
35
  const fieldStyles = {
36
- container: Css_1.Css.df.fdc.w100.maxw((0, Css_1.px)(550)).$,
36
+ container: Css_1.Css.df.fdc.w100.maxw((0, Css_1.px)(550)).relative.$,
37
37
  inputWrapper: {
38
38
  ...Css_1.Css[typeScale].df.aic.br4.px1.w100
39
39
  .hPx(fieldHeight - maybeSmaller)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homebound/beam",
3
- "version": "2.101.1",
3
+ "version": "2.101.5",
4
4
  "author": "Homebound",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -34,6 +34,7 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@homebound/form-state": "^2.2.13",
37
+ "@internationalized/number": "^3.0.3",
37
38
  "@react-aria/utils": "^3.9.0",
38
39
  "@react-hook/resize-observer": "^1.2.2",
39
40
  "@types/tinycolor2": "^1.4.2",