@xh/hoist 77.0.0-SNAPSHOT.1761690719868 → 77.0.0-SNAPSHOT.1761769225395

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
@@ -1,12 +1,14 @@
1
1
  # Changelog
2
2
 
3
- ## 77.0.0-SNAPSHOT - unreleased
3
+ ## 76.2.1 - 2025-10-29
4
4
 
5
5
  ### 🐞 Bug Fixes
6
6
  * Fixes regressions in grid context menu for filtering and copy/paste introduced by agGrid v34.
7
+ * Fixes `getExpandState` in `AgGridModel`
7
8
 
8
- * Note: AgGrid no longer supports html markup in context menus. Applications setting the
9
- RecordGridAction `text` property to markup should be sure to convert to a simpler form.
9
+ * Note: As of v34, AgGrid no longer supports html markup in context menus. Applications setting
10
+ the `text` or `secondaryText` properties of `RecordGridAction` to markup should be sure to use
11
+ react nodes for formatting instead.
10
12
 
11
13
  ### 💥 Breaking Changes
12
14
 
@@ -1,6 +1,6 @@
1
1
  import { GridModel } from '@xh/hoist/cmp/grid';
2
- import { GridContextMenuSpec } from '../GridContextMenu';
3
- import type { GetContextMenuItemsParams, MenuItemDef } from '@xh/hoist/kit/ag-grid';
2
+ import { type GetContextMenuItemsParams, type MenuItemDef } from '@xh/hoist/kit/ag-grid';
3
+ import type { GridContextMenuSpec } from '../GridContextMenu';
4
4
  /**
5
5
  * @internal
6
6
  */
@@ -1,12 +1,12 @@
1
- import { ReactElement } from 'react';
2
- import { Intent, PlainObject, TestSupportProps } from '../core';
1
+ import { ReactElement, ReactNode } from 'react';
2
+ import { Intent, TestSupportProps } from '../core';
3
3
  import { StoreRecord } from './StoreRecord';
4
4
  import { Column, GridModel } from '../cmp/grid';
5
5
  export interface RecordActionSpec extends TestSupportProps {
6
6
  /** Label to be displayed. */
7
- text?: string;
7
+ text?: ReactNode;
8
8
  /** Additional label to be displayed, usually in a minimal fashion.*/
9
- secondaryText?: string;
9
+ secondaryText?: ReactNode;
10
10
  /** Icon to be displayed.*/
11
11
  icon?: ReactElement;
12
12
  /** Intent to be used for rendering the action.*/
@@ -72,15 +72,15 @@ export interface ActionFnData {
72
72
  * @see GridContextMenuSpec
73
73
  */
74
74
  export declare class RecordAction {
75
- text: string;
76
- secondaryText: string;
75
+ text: ReactNode;
76
+ secondaryText: ReactNode;
77
77
  icon: ReactElement;
78
78
  intent: Intent;
79
79
  className: string;
80
80
  tooltip: string;
81
81
  actionFn: (data: ActionFnData) => void;
82
- displayFn: (data: ActionFnData) => PlainObject;
83
- items: Array<RecordAction | string>;
82
+ displayFn: (data: ActionFnData) => RecordActionSpec;
83
+ items: RecordActionLike[];
84
84
  disabled: boolean;
85
85
  hidden: boolean;
86
86
  recordsRequired: boolean | number;
@@ -90,17 +90,7 @@ export declare class RecordAction {
90
90
  * Called by UI elements to get the display configuration for rendering the action.
91
91
  * @internal
92
92
  */
93
- getDisplaySpec({ record, selectedRecords, gridModel, column, ...rest }: ActionFnData): {
94
- icon: ReactElement<any, string | import("react").JSXElementConstructor<any>>;
95
- text: string;
96
- secondaryText: string;
97
- intent: Intent;
98
- className: string;
99
- tooltip: string;
100
- items: (string | RecordAction)[];
101
- hidden: boolean;
102
- disabled: boolean;
103
- };
93
+ getDisplaySpec({ record, selectedRecords, gridModel, column, ...rest }: ActionFnData): RecordActionSpec;
104
94
  /**
105
95
  * Called by UI elements to trigger the action.
106
96
  * @internal
@@ -13,8 +13,8 @@ export declare let agGridVersion: any;
13
13
  * implementations.
14
14
  */
15
15
  export type { GridOptions, GridApi, SortDirection, ColDef, ColGroupDef, GetContextMenuItemsParams, GridReadyEvent, IHeaderGroupParams, IHeaderParams, ProcessCellForExportParams, CellClassParams, HeaderClassParams, HeaderValueGetterParams, ICellRendererParams, ITooltipParams, IRowNode, RowClassParams, ValueGetterParams, ValueSetterParams, MenuItemDef, CellPosition, NavigateToNextCellParams, ColumnEvent, ColumnState as AgColumnState, Column as AgColumn, ColumnGroup as AgColumnGroup, AgProvidedColumnGroup, RowDoubleClickedEvent, RowClickedEvent, RowHeightParams, CellClickedEvent, CellContextMenuEvent, CellDoubleClickedEvent, CellEditingStartedEvent, CellEditingStoppedEvent } from 'ag-grid-community';
16
- export type { CustomCellEditorProps } from 'ag-grid-react';
17
- export { useGridCellEditor } from 'ag-grid-react';
16
+ export type { CustomCellEditorProps, CustomMenuItemProps } from 'ag-grid-react';
17
+ export { useGridCellEditor, useGridMenuItem } from 'ag-grid-react';
18
18
  /**
19
19
  * Expose application versions of ag-Grid to Hoist.
20
20
  * Typically called in the Bootstrap.js. of the application.
@@ -18,6 +18,7 @@ import {
18
18
  isEmpty,
19
19
  isEqual,
20
20
  isNil,
21
+ isObject,
21
22
  partition,
22
23
  setWith,
23
24
  startCase
@@ -415,27 +416,23 @@ export class AgGridModel extends HoistModel {
415
416
 
416
417
  const expandState = {};
417
418
  this.agApi.forEachNode(node => {
418
- if (!node.allChildrenCount) return;
419
-
420
- if (node.expanded) {
421
- // Skip if parent is collapsed. Parents are visited before children,
422
- // so should already be in expandState if expanded.
423
- const parent = node.parent;
424
- if (
425
- parent &&
426
- parent.id !== 'ROOT_NODE_ID' &&
427
- !has(expandState, this.getGroupNodePath(parent))
428
- ) {
429
- return;
430
- }
431
-
432
- // Note use of setWith + customizer - required to ensure that nested nodes are
433
- // serialized as objects - see https://github.com/xh/hoist-react/issues/3550.
434
- const path = this.getGroupNodePath(node);
435
- setWith(expandState, path, true, () => ({}));
419
+ if (!node.allChildrenCount || !node.expanded) return;
420
+ // Skip if parent is collapsed. Parents are visited before children,
421
+ // so should already be in expandState if expanded.
422
+ const parent = node.parent;
423
+ if (
424
+ parent &&
425
+ parent.id !== 'ROOT_NODE_ID' &&
426
+ !has(expandState, this.getGroupNodePath(parent))
427
+ ) {
428
+ return;
436
429
  }
437
- });
438
430
 
431
+ const path = this.getGroupNodePath(node);
432
+ // Note use of setWith + customizer - required to ensure that nested nodes are
433
+ // serialized as objects - see https://github.com/xh/hoist-react/issues/3550.
434
+ setWith(expandState, path, true, nsValue => (isObject(nsValue) ? nsValue : {}));
435
+ });
439
436
  return expandState;
440
437
  }
441
438
 
@@ -4,18 +4,22 @@
4
4
  *
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
- import {Some, XH} from '@xh/hoist/core';
7
+ import {isEmpty, isFunction, isNil, isString, uniq} from 'lodash';
8
+ import copy from 'clipboard-copy';
9
+ import {hoistCmp, type HoistProps, type Some, XH} from '@xh/hoist/core';
8
10
  import {Column, GridModel} from '@xh/hoist/cmp/grid';
9
- import {RecordAction, Store, StoreRecord} from '@xh/hoist/data';
10
- import {convertIconToHtml, Icon} from '@xh/hoist/icon';
11
+ import {RecordAction, type RecordActionSpec, Store, StoreRecord} from '@xh/hoist/data';
12
+ import {Icon} from '@xh/hoist/icon';
11
13
  import {filterConsecutiveMenuSeparators} from '@xh/hoist/utils/impl';
12
- import copy from 'clipboard-copy';
13
- import {isEmpty, isFunction, isNil, isString, uniq} from 'lodash';
14
- import {isValidElement} from 'react';
15
- import {GridContextMenuItemLike, GridContextMenuSpec} from '../GridContextMenu';
16
-
17
- import type {GetContextMenuItemsParams, MenuItemDef} from '@xh/hoist/kit/ag-grid';
18
14
  import {wait} from '@xh/hoist/promise';
15
+ import {div, span} from '@xh/hoist/cmp/layout';
16
+ import {
17
+ useGridMenuItem,
18
+ type GetContextMenuItemsParams,
19
+ type MenuItemDef,
20
+ type CustomMenuItemProps
21
+ } from '@xh/hoist/kit/ag-grid';
22
+ import type {GridContextMenuItemLike, GridContextMenuSpec} from '../GridContextMenu';
19
23
 
20
24
  /**
21
25
  * @internal
@@ -75,22 +79,22 @@ function buildMenuItems(
75
79
  subMenu = buildMenuItems(displaySpec.items, record, gridModel, column, agParams);
76
80
  }
77
81
 
78
- const icon = isValidElement(displaySpec.icon) ? convertIconToHtml(displaySpec.icon) : null;
79
-
80
82
  const cssClasses = ['xh-grid-menu-option'];
81
83
  if (displaySpec.intent)
82
84
  cssClasses.push(`xh-grid-menu-option--intent-${displaySpec.intent}`);
83
85
  if (displaySpec.className) cssClasses.push(displaySpec.className);
84
86
 
85
87
  ret.push({
86
- name: displaySpec.text,
87
- shortcut: displaySpec.secondaryText,
88
- icon,
88
+ menuItem: RecordActionMenuItem,
89
+ menuItemParams: {
90
+ displaySpec
91
+ },
92
+ // Standard MenuActionProps
89
93
  cssClasses,
90
94
  subMenu,
91
95
  tooltip: displaySpec.tooltip,
92
96
  disabled: displaySpec.disabled,
93
- // Avoid specifying action if no handler, allows submenus to remain open if accidentally clicked
97
+ // Don't specify action if no handler, allows submenus to remain open if clicked
94
98
  action: action.actionFn ? () => action.call(actionParams) : undefined
95
99
  });
96
100
  });
@@ -178,17 +182,16 @@ function replaceHoistToken(token: string, gridModel: GridModel): Some<RecordActi
178
182
  const values = getValues(selectedRecords, field);
179
183
  if (values.length > 1) return {text: `${values.length} values`};
180
184
 
181
- // Grid col renderers will very typically return elements, but we need this to be a string.
182
- // As of AG v34.2.0 actions into Ag-Grid context menus *only* accept strings.
183
- let raw = values[0],
184
- renderer = fieldSpec.renderer ?? column.renderer,
185
- text = renderer?.(raw, {record, column, gridModel});
186
- if (!isString(text)) {
187
- text = raw?.toString();
188
- }
189
- text = text?.trim();
185
+ const renderer = fieldSpec.renderer ?? column.renderer,
186
+ elem = renderer
187
+ ? renderer(values[0], {
188
+ record,
189
+ column,
190
+ gridModel
191
+ })
192
+ : (values[0] ?? '[blank]');
190
193
 
191
- return {text: text ?? '[blank]'};
194
+ return {text: elem};
192
195
  };
193
196
 
194
197
  return new RecordAction({
@@ -297,3 +300,43 @@ function levelExpandAction(gridModel: GridModel): RecordAction {
297
300
  }
298
301
  });
299
302
  }
303
+
304
+ /**
305
+ * A MenuItem for a Hoist RecordAction.
306
+ *
307
+ * A variant of the standard ag-Grid Context menu. Unlike built-in ag-Grid menu item,
308
+ * provides support for specifying 'text' and 'shortcut' display as react elements.
309
+ *
310
+ * @internal
311
+ */
312
+
313
+ interface RecordActionMenuItemProps extends HoistProps, CustomMenuItemProps {
314
+ displaySpec: RecordActionSpec;
315
+ }
316
+
317
+ const RecordActionMenuItem = hoistCmp<RecordActionMenuItemProps>({
318
+ render({displaySpec, subMenu}: RecordActionMenuItemProps) {
319
+ useGridMenuItem({
320
+ configureDefaults: () => true
321
+ });
322
+
323
+ return div(
324
+ span({className: 'ag-menu-option-part ag-menu-option-icon', item: displaySpec.icon}),
325
+ span({className: 'ag-menu-option-part ag-menu-option-text', item: displaySpec.text}),
326
+ span({
327
+ className: 'ag-menu-option-part ag-menu-option-shortcut',
328
+ item: displaySpec.secondaryText
329
+ }),
330
+ span({
331
+ className: 'ag-menu-option-part ag-menu-option-popup-pointer',
332
+ item: subMenu
333
+ ? span({
334
+ className: 'ag-icon ag-icon-small-right',
335
+ unselectable: 'on',
336
+ role: 'presentation'
337
+ })
338
+ : ''
339
+ })
340
+ );
341
+ }
342
+ });
@@ -6,17 +6,17 @@
6
6
  */
7
7
 
8
8
  import {isBoolean, isEmpty, isNil, isNumber, isString} from 'lodash';
9
- import {ReactElement} from 'react';
10
- import {Intent, PlainObject, TestSupportProps} from '../core';
9
+ import {ReactElement, ReactNode} from 'react';
10
+ import {Intent, TestSupportProps} from '../core';
11
11
  import {StoreRecord} from './StoreRecord';
12
12
  import {Column, GridModel} from '../cmp/grid';
13
13
 
14
14
  export interface RecordActionSpec extends TestSupportProps {
15
15
  /** Label to be displayed. */
16
- text?: string;
16
+ text?: ReactNode;
17
17
 
18
18
  /** Additional label to be displayed, usually in a minimal fashion.*/
19
- secondaryText?: string;
19
+ secondaryText?: ReactNode;
20
20
 
21
21
  /** Icon to be displayed.*/
22
22
  icon?: ReactElement;
@@ -100,15 +100,15 @@ export interface ActionFnData {
100
100
  * @see GridContextMenuSpec
101
101
  */
102
102
  export class RecordAction {
103
- text: string;
104
- secondaryText: string;
103
+ text: ReactNode;
104
+ secondaryText: ReactNode;
105
105
  icon: ReactElement;
106
106
  intent: Intent;
107
107
  className: string;
108
108
  tooltip: string;
109
109
  actionFn: (data: ActionFnData) => void;
110
- displayFn: (data: ActionFnData) => PlainObject;
111
- items: Array<RecordAction | string>;
110
+ displayFn: (data: ActionFnData) => RecordActionSpec;
111
+ items: RecordActionLike[];
112
112
  disabled: boolean;
113
113
  hidden: boolean;
114
114
  recordsRequired: boolean | number;
@@ -152,11 +152,17 @@ export class RecordAction {
152
152
  * Called by UI elements to get the display configuration for rendering the action.
153
153
  * @internal
154
154
  */
155
- getDisplaySpec({record, selectedRecords, gridModel, column, ...rest}: ActionFnData) {
155
+ getDisplaySpec({
156
+ record,
157
+ selectedRecords,
158
+ gridModel,
159
+ column,
160
+ ...rest
161
+ }: ActionFnData): RecordActionSpec {
156
162
  const recordCount =
157
163
  record && isEmpty(selectedRecords) ? 1 : selectedRecords ? selectedRecords.length : 0;
158
164
 
159
- const defaultDisplay = {
165
+ const defaultDisplay: RecordActionSpec = {
160
166
  icon: this.icon,
161
167
  text: this.text,
162
168
  secondaryText: this.secondaryText,
@@ -60,8 +60,8 @@ export type {
60
60
  CellEditingStoppedEvent
61
61
  } from 'ag-grid-community';
62
62
 
63
- export type {CustomCellEditorProps} from 'ag-grid-react';
64
- export {useGridCellEditor} from 'ag-grid-react';
63
+ export type {CustomCellEditorProps, CustomMenuItemProps} from 'ag-grid-react';
64
+ export {useGridCellEditor, useGridMenuItem} from 'ag-grid-react';
65
65
 
66
66
  const MIN_VERSION = '34.2.0';
67
67
  const MAX_VERSION = '34.*.*';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "77.0.0-SNAPSHOT.1761690719868",
3
+ "version": "77.0.0-SNAPSHOT.1761769225395",
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",