@xh/hoist 76.0.0-SNAPSHOT.1757428490497 → 76.0.0-SNAPSHOT.1757966316621

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.
package/CHANGELOG.md CHANGED
@@ -22,6 +22,9 @@
22
22
  views will be pinned by default. This feature was deemed too confusing, and not useful in
23
23
  practice. App maintainers should ensure that all global views are appropriate and well
24
24
  organized enough to be shown immediately to new users in the view menu.
25
+ * New constraint rule: `validEmails` - to validate one or more email addresses in an input field.
26
+ * `DashCanvas` accepts a new prop `rglOptions` to pass additional options to the underlying
27
+ `react-grid-layout`.
25
28
 
26
29
  ### 🐞 Bug Fixes
27
30
 
@@ -10,10 +10,21 @@ import { Constraint } from './Rule';
10
10
  */
11
11
  export declare const required: Constraint;
12
12
  /**
13
- * Validate an email address.
14
- * https://stackoverflow.com/questions/46155/how-to-validate-an-email-address-in-javascript/46181#46181.
13
+ * Validate a single email address in a field that expects only one email address.
15
14
  */
16
15
  export declare const validEmail: Constraint<string>;
16
+ /**
17
+ * Validate all email addresses in a field that allows multiple email addresses
18
+ * separated by a semicolon - the separator used by Outlook for multiple email addresses.
19
+ * All email addresses must be unique.
20
+ */
21
+ export declare function validEmails(c?: ValidEmailsOptions): Constraint<string>;
22
+ export interface ValidEmailsOptions {
23
+ /** Require at least N email addresses. */
24
+ minCount?: number;
25
+ /** Require no more than N email addresses. */
26
+ maxCount?: number;
27
+ }
17
28
  /** Validate length of a string.*/
18
29
  export declare function lengthIs(c: LengthIsOptions): Constraint<string>;
19
30
  export interface LengthIsOptions {
@@ -1,9 +1,18 @@
1
1
  import { HoistProps, TestSupportProps } from '@xh/hoist/core';
2
2
  import '@xh/hoist/desktop/register';
3
+ import type { ReactGridLayoutProps } from 'react-grid-layout';
3
4
  import { DashCanvasModel } from './DashCanvasModel';
4
5
  import 'react-grid-layout/css/styles.css';
5
6
  import './DashCanvas.scss';
6
- export type DashCanvasProps = HoistProps<DashCanvasModel> & TestSupportProps;
7
+ export interface DashCanvasProps extends HoistProps<DashCanvasModel>, TestSupportProps {
8
+ /**
9
+ * Optional additional configuration options to pass through to the underlying ReactGridLayout component.
10
+ * See the RGL documentation for details:
11
+ * {@link https://www.npmjs.com/package/react-grid-layout#grid-layout-props}
12
+ * Note that some ReactGridLayout props are managed directly by DashCanvas and will be overridden if provided here.
13
+ */
14
+ rglOptions?: ReactGridLayoutProps;
15
+ }
7
16
  /**
8
17
  * Dashboard-style container that allows users to drag-and-drop child widgets into flexible layouts.
9
18
  *
@@ -5,7 +5,7 @@
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
7
  import {LocalDate} from '@xh/hoist/utils/datetime';
8
- import {isArray, isEmpty, isFinite, isNil, isString} from 'lodash';
8
+ import {isArray, isEmpty, isFinite, isNil, isString, uniq} from 'lodash';
9
9
  import moment from 'moment';
10
10
  import {Constraint} from './Rule';
11
11
  /**
@@ -27,19 +27,52 @@ export const required: Constraint = ({value, displayName}) => {
27
27
  };
28
28
 
29
29
  /**
30
- * Validate an email address.
31
- * https://stackoverflow.com/questions/46155/how-to-validate-an-email-address-in-javascript/46181#46181.
30
+ * Validate a single email address in a field that expects only one email address.
32
31
  */
33
32
  export const validEmail: Constraint<string> = ({value, displayName}) => {
34
33
  if (isNil(value)) return null;
35
34
 
36
- // prettier-ignore
37
- // eslint-disable-next-line no-useless-escape
38
- const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
39
- isValid = re.test(value);
40
- if (!isValid) return `${displayName} is not a properly formatted address.`;
35
+ const isValid = emailRegEx.test(value);
36
+ if (!isValid) return `${displayName} is not a properly formatted email address.`;
41
37
  };
42
38
 
39
+ /**
40
+ * Validate all email addresses in a field that allows multiple email addresses
41
+ * separated by a semicolon - the separator used by Outlook for multiple email addresses.
42
+ * All email addresses must be unique.
43
+ */
44
+ export function validEmails(c?: ValidEmailsOptions): Constraint<string> {
45
+ return ({value, displayName}) => {
46
+ if (isNil(value)) return null;
47
+
48
+ const emails = value
49
+ .split(';')
50
+ .map(it => it.trim())
51
+ .filter(Boolean);
52
+
53
+ if (uniq(emails).length !== emails.length) {
54
+ return `${displayName} must not contain duplicate email addresses.`;
55
+ }
56
+ if (emails.length < c?.minCount) {
57
+ return `${displayName} must contain at least ${c.minCount} email addresses.`;
58
+ }
59
+ if (!isNil(c?.maxCount) && emails.length > c.maxCount) {
60
+ return `${displayName} must contain no more than ${c.maxCount} email addresses.`;
61
+ }
62
+
63
+ const isValid = emails.every(it => emailRegEx.test(it));
64
+
65
+ if (!isValid) return `${displayName} has an improperly formatted email address.`;
66
+ };
67
+ }
68
+ export interface ValidEmailsOptions {
69
+ /** Require at least N email addresses. */
70
+ minCount?: number;
71
+
72
+ /** Require no more than N email addresses. */
73
+ maxCount?: number;
74
+ }
75
+
43
76
  /** Validate length of a string.*/
44
77
  export function lengthIs(c: LengthIsOptions): Constraint<string> {
45
78
  return ({value, displayName}) => {
@@ -197,3 +230,8 @@ export const isValidJson: Constraint = ({value, displayName}) => {
197
230
  return `${displayName} is not valid JSON`;
198
231
  }
199
232
  };
233
+
234
+ // https://stackoverflow.com/questions/46155/how-to-validate-an-email-address-in-javascript/46181#46181
235
+ // prettier-ignore
236
+ // eslint-disable-next-line no-useless-escape
237
+ const emailRegEx = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
@@ -21,6 +21,7 @@ import {Classes, overlay} from '@xh/hoist/kit/blueprint';
21
21
  import {consumeEvent, TEST_ID} from '@xh/hoist/utils/js';
22
22
  import classNames from 'classnames';
23
23
  import ReactGridLayout, {WidthProvider} from 'react-grid-layout';
24
+ import type {ReactGridLayoutProps} from 'react-grid-layout';
24
25
  import {DashCanvasModel} from './DashCanvasModel';
25
26
  import {dashCanvasContextMenu} from './impl/DashCanvasContextMenu';
26
27
  import {dashCanvasView} from './impl/DashCanvasView';
@@ -28,7 +29,15 @@ import {dashCanvasView} from './impl/DashCanvasView';
28
29
  import 'react-grid-layout/css/styles.css';
29
30
  import './DashCanvas.scss';
30
31
 
31
- export type DashCanvasProps = HoistProps<DashCanvasModel> & TestSupportProps;
32
+ export interface DashCanvasProps extends HoistProps<DashCanvasModel>, TestSupportProps {
33
+ /**
34
+ * Optional additional configuration options to pass through to the underlying ReactGridLayout component.
35
+ * See the RGL documentation for details:
36
+ * {@link https://www.npmjs.com/package/react-grid-layout#grid-layout-props}
37
+ * Note that some ReactGridLayout props are managed directly by DashCanvas and will be overridden if provided here.
38
+ */
39
+ rglOptions?: ReactGridLayoutProps;
40
+ }
32
41
 
33
42
  /**
34
43
  * Dashboard-style container that allows users to drag-and-drop child widgets into flexible layouts.
@@ -46,7 +55,7 @@ export const [DashCanvas, dashCanvas] = hoistCmp.withFactory<DashCanvasProps>({
46
55
  className: 'xh-dash-canvas',
47
56
  model: uses(DashCanvasModel),
48
57
 
49
- render({className, model, testId}, ref) {
58
+ render({className, model, rglOptions, testId}, ref) {
50
59
  const isDraggable = !model.layoutLocked,
51
60
  isResizable = !model.layoutLocked,
52
61
  [padX, padY] = model.containerPadding;
@@ -86,7 +95,8 @@ export const [DashCanvas, dashCanvas] = hoistCmp.withFactory<DashCanvasProps>({
86
95
  key: vm.id,
87
96
  item: dashCanvasView({model: vm})
88
97
  })
89
- )
98
+ ),
99
+ ...rglOptions
90
100
  }),
91
101
  emptyContainerOverlay()
92
102
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "76.0.0-SNAPSHOT.1757428490497",
3
+ "version": "76.0.0-SNAPSHOT.1757966316621",
4
4
  "description": "Hoist add-on for building and deploying React Applications.",
5
5
  "repository": "github:xh/hoist-react",
6
6
  "homepage": "https://xh.io",
@@ -96,6 +96,7 @@
96
96
  "@ag-grid-community/react": "31.x",
97
97
  "@types/react": "18.x",
98
98
  "@types/react-dom": "18.x",
99
+ "@types/react-grid-layout": "1.3.5",
99
100
  "@xh/hoist-dev-utils": "11.x",
100
101
  "csstype": "3.x",
101
102
  "eslint": "9.x",