@genesislcap/foundation-react-utils 14.481.2-alpha-829ad82.0 → 14.482.1-FUI-2575.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.
- package/dist/custom-elements.json +171 -0
- package/dist/dts/create-grid-pro-cell-portals.d.ts +81 -0
- package/dist/dts/create-grid-pro-cell-portals.d.ts.map +1 -0
- package/dist/dts/create-grid-pro-cell-renderer.d.ts +140 -0
- package/dist/dts/create-grid-pro-cell-renderer.d.ts.map +1 -0
- package/dist/dts/index.d.ts +7 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/esm/create-grid-pro-cell-portals.js +138 -0
- package/dist/esm/create-grid-pro-cell-renderer.js +141 -0
- package/dist/esm/index.js +5 -0
- package/dist/foundation-react-utils.api.json +763 -0
- package/dist/foundation-react-utils.d.ts +223 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +13 -13
- package/src/create-grid-pro-cell-portals.tsx +216 -0
- package/src/create-grid-pro-cell-renderer.ts +235 -0
- package/src/index.ts +11 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { createLogger } from '@genesislcap/foundation-logger';
|
|
2
|
+
import { createElement, type ComponentType } from 'react';
|
|
3
|
+
import { flushSync } from 'react-dom';
|
|
4
|
+
import { createRoot, type Root } from 'react-dom/client';
|
|
5
|
+
import type { GridProCellPortals } from './create-grid-pro-cell-portals';
|
|
6
|
+
|
|
7
|
+
const logger = createLogger('foundation-react-utils');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Props passed to a React component used as a Grid Pro cell renderer.
|
|
11
|
+
*
|
|
12
|
+
* This is a structural subset of AG Grid's `ICellRendererParams` — the full params object
|
|
13
|
+
* (including any custom `cellRendererParams` from the column definition) is spread onto the
|
|
14
|
+
* component as props on every render, so anything available on `ICellRendererParams` is
|
|
15
|
+
* available here. Declared locally to avoid coupling this package to a specific
|
|
16
|
+
* `ag-grid-community` version.
|
|
17
|
+
*
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
20
|
+
export interface GridProCellRendererProps<TData = any, TValue = any> {
|
|
21
|
+
/** The cell value. */
|
|
22
|
+
value: TValue;
|
|
23
|
+
/** Value formatted by the column's value formatter, if any. */
|
|
24
|
+
valueFormatted?: string | null;
|
|
25
|
+
/** The full row data. */
|
|
26
|
+
data: TData;
|
|
27
|
+
/** The AG Grid row node. */
|
|
28
|
+
node: any;
|
|
29
|
+
/** The AG Grid api. */
|
|
30
|
+
api: any;
|
|
31
|
+
/** The row index of the cell. */
|
|
32
|
+
rowIndex: number;
|
|
33
|
+
/** The grid context object, as supplied via `gridOptions.context`. */
|
|
34
|
+
context?: any;
|
|
35
|
+
/** The column this cell belongs to. */
|
|
36
|
+
column?: any;
|
|
37
|
+
/** The column definition. */
|
|
38
|
+
colDef?: any;
|
|
39
|
+
/** Custom params from `cellRendererParams` and any other AG Grid params. */
|
|
40
|
+
[key: string]: any;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Options for {@link createGridProCellRenderer}.
|
|
45
|
+
*
|
|
46
|
+
* @public
|
|
47
|
+
*/
|
|
48
|
+
export interface GridProCellRendererOptions {
|
|
49
|
+
/**
|
|
50
|
+
* When true (the default), the cell container is styled as a full-size flex row
|
|
51
|
+
* (`display: flex; align-items: center; height: 100%; width: 100%`) so content is
|
|
52
|
+
* vertically centred like grid-pro's built-in renderers.
|
|
53
|
+
*
|
|
54
|
+
* Set to false to leave the container unstyled and let the cell's own layout apply.
|
|
55
|
+
* Do this when the forced flex/height layout gets in the way, e.g.:
|
|
56
|
+
* - `autoHeight`/`wrapText` columns — `height: 100%` is circular when the row height
|
|
57
|
+
* derives from the content, so the cell can collapse instead of growing;
|
|
58
|
+
* - right-aligned numeric cells relying on `text-align` (a flex item packs to the start);
|
|
59
|
+
* - content using `text-overflow: ellipsis` (a flex item won't truncate without
|
|
60
|
+
* `min-width: 0`).
|
|
61
|
+
*/
|
|
62
|
+
fillCell?: boolean;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Render cells through a shared {@link GridProCellPortals} manager instead of giving each
|
|
66
|
+
* cell its own React root. Portal-rendered cells become part of the React tree where the
|
|
67
|
+
* manager's `Portals` component is rendered, so they inherit React Context (Redux, theme,
|
|
68
|
+
* router, etc.) from the application — the main limitation of the default mode.
|
|
69
|
+
*
|
|
70
|
+
* Create one with `createGridProCellPortals()` and render `<manager.Portals />` once,
|
|
71
|
+
* inside the providers the cells should see. When omitted (the default), each cell gets
|
|
72
|
+
* its own isolated root.
|
|
73
|
+
*/
|
|
74
|
+
portals?: GridProCellPortals;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The subset of AG Grid's `ICellRendererComp` contract implemented by the wrapper class
|
|
79
|
+
* returned from {@link createGridProCellRenderer}.
|
|
80
|
+
*
|
|
81
|
+
* @public
|
|
82
|
+
*/
|
|
83
|
+
export interface GridProReactCellRenderer {
|
|
84
|
+
init(params: GridProCellRendererProps): void;
|
|
85
|
+
getGui(): HTMLElement;
|
|
86
|
+
refresh(params: GridProCellRendererProps): boolean;
|
|
87
|
+
destroy(): void;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Wraps a React component as an AG Grid cell renderer component class for use with
|
|
92
|
+
* `grid-pro` / `grid-pro-beta` (`<foundation-grid-pro>` / `<rapid-grid-pro>`).
|
|
93
|
+
*
|
|
94
|
+
* Grid Pro is a framework-agnostic Web Component, so AG Grid inside it only understands
|
|
95
|
+
* plain-JS component classes. This helper bridges the gap: it returns a class implementing
|
|
96
|
+
* `ICellRendererComp` that mounts the React component into the cell with its own React root,
|
|
97
|
+
* re-renders it on `refresh` (so value changes and cell flashing keep working without
|
|
98
|
+
* remounting), and unmounts it when the cell is destroyed.
|
|
99
|
+
*
|
|
100
|
+
* The component receives the full AG Grid `ICellRendererParams` object as props — including
|
|
101
|
+
* `value`, `data`, `node`, `api` and anything supplied via the column's `cellRendererParams`.
|
|
102
|
+
* Hooks and local state inside the component work as normal.
|
|
103
|
+
*
|
|
104
|
+
* Note on React Context/Redux: by default each cell is rendered into its own independent
|
|
105
|
+
* React root, so renderers do NOT inherit contexts (Redux providers, theme context, React
|
|
106
|
+
* Router, etc.) from the application tree hosting the grid. Either pass data/callbacks
|
|
107
|
+
* explicitly via `cellRendererParams`, or opt into portal mode with the `portals` option
|
|
108
|
+
* (see {@link GridProCellRendererOptions.portals} and `createGridProCellPortals`) — portal
|
|
109
|
+
* cells share the application tree and inherit its contexts.
|
|
110
|
+
*
|
|
111
|
+
* Note on `key`: props are passed by spreading the AG Grid params object, and React reserves
|
|
112
|
+
* `key` — a `cellRendererParams` entry named `key` is consumed as the element key and never
|
|
113
|
+
* reaches the component, so avoid that name. (`ref` is fine: React 19 forwards it to function
|
|
114
|
+
* components as a regular prop.)
|
|
115
|
+
*
|
|
116
|
+
* Note on styling: cells render inside grid-pro's shadow root, so document-level stylesheets
|
|
117
|
+
* (CSS files imported by your app/page) do NOT reach the rendered component. Style renderers
|
|
118
|
+
* with inline styles or CSS-in-JS. Design-system tokens remain usable either way — CSS custom
|
|
119
|
+
* properties inherit across shadow boundaries (e.g. `color: 'var(--accent-fill-rest)'`).
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```tsx
|
|
123
|
+
* import { createGridProCellRenderer, type GridProCellRendererProps } from '@genesislcap/foundation-react-utils';
|
|
124
|
+
*
|
|
125
|
+
* function PriceCell({ value }: GridProCellRendererProps) {
|
|
126
|
+
* return <span style={{ color: value >= 0 ? 'green' : 'red' }}>{value}</span>;
|
|
127
|
+
* }
|
|
128
|
+
*
|
|
129
|
+
* const gridOptions = {
|
|
130
|
+
* components: {
|
|
131
|
+
* priceCell: createGridProCellRenderer(PriceCell),
|
|
132
|
+
* },
|
|
133
|
+
* columnDefs: [
|
|
134
|
+
* { field: 'price', cellRenderer: 'priceCell' },
|
|
135
|
+
* ],
|
|
136
|
+
* };
|
|
137
|
+
*
|
|
138
|
+
* // <rapid-grid-pro gridOptions={gridOptions} /> — or register via the grid's
|
|
139
|
+
* // `gridComponents` property to make the renderer available across all columns.
|
|
140
|
+
* ```
|
|
141
|
+
*
|
|
142
|
+
* @param Component - The React component to render inside the cell.
|
|
143
|
+
* @param options - Optional {@link GridProCellRendererOptions} to tune the wrapper behaviour.
|
|
144
|
+
* @returns A cell renderer component class registrable via `gridOptions.components`,
|
|
145
|
+
* the grid's `gridComponents` property, or directly as a column's `cellRenderer`.
|
|
146
|
+
*
|
|
147
|
+
* @public
|
|
148
|
+
*/
|
|
149
|
+
export function createGridProCellRenderer<
|
|
150
|
+
P extends GridProCellRendererProps = GridProCellRendererProps,
|
|
151
|
+
>(
|
|
152
|
+
Component: ComponentType<P>,
|
|
153
|
+
options: GridProCellRendererOptions = {},
|
|
154
|
+
): new () => GridProReactCellRenderer {
|
|
155
|
+
const fillCell = options.fillCell !== false;
|
|
156
|
+
const portals = options.portals;
|
|
157
|
+
|
|
158
|
+
return class ReactCellRenderer implements GridProReactCellRenderer {
|
|
159
|
+
private container!: HTMLElement;
|
|
160
|
+
private root: Root | null = null;
|
|
161
|
+
private portalId: number | null = null;
|
|
162
|
+
|
|
163
|
+
public init(params: P): void {
|
|
164
|
+
this.container = document.createElement('div');
|
|
165
|
+
if (fillCell) {
|
|
166
|
+
// Fill the cell so the React content can align itself like native renderers do.
|
|
167
|
+
// See GridProCellRendererOptions.fillCell for when to opt out.
|
|
168
|
+
this.container.style.display = 'flex';
|
|
169
|
+
this.container.style.alignItems = 'center';
|
|
170
|
+
this.container.style.height = '100%';
|
|
171
|
+
this.container.style.width = '100%';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (portals) {
|
|
175
|
+
// Portal mode: the shared manager renders the component into this container as
|
|
176
|
+
// part of the application tree (mount commits synchronously so AG Grid measures
|
|
177
|
+
// real content — same reasoning as the flushSync below).
|
|
178
|
+
this.portalId = portals.mount(this.container, Component, params);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
this.root = createRoot(this.container);
|
|
183
|
+
// React 19 concurrent roots only *schedule* render(), but AG Grid inserts and can
|
|
184
|
+
// synchronously measure the container as soon as init/getGui return (autoHeight,
|
|
185
|
+
// autoSizeColumns, sizeColumnsToFit) — flush the first render so the grid measures
|
|
186
|
+
// real content instead of an empty div (also avoids a one-frame empty flash).
|
|
187
|
+
// Cell init runs from AG Grid's own (non-React) call stack, so flushing is safe
|
|
188
|
+
// here; if it ever runs inside a host React render, React warns and falls back to
|
|
189
|
+
// a deferred flush rather than breaking. refresh() intentionally stays async.
|
|
190
|
+
flushSync(() => {
|
|
191
|
+
this.root!.render(createElement(Component, params));
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
public getGui(): HTMLElement {
|
|
196
|
+
return this.container;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
public refresh(params: P): boolean {
|
|
200
|
+
if (portals) {
|
|
201
|
+
if (this.portalId != null) {
|
|
202
|
+
portals.update(this.portalId, params);
|
|
203
|
+
}
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
this.root?.render(createElement(Component, params));
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
public destroy(): void {
|
|
211
|
+
if (portals) {
|
|
212
|
+
if (this.portalId != null) {
|
|
213
|
+
portals.remove(this.portalId);
|
|
214
|
+
this.portalId = null;
|
|
215
|
+
}
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const root = this.root;
|
|
220
|
+
this.root = null;
|
|
221
|
+
if (root) {
|
|
222
|
+
// AG Grid can destroy cells synchronously while React is rendering (e.g. React Router
|
|
223
|
+
// unmounting the page that hosts the grid). Unmounting a root mid-render is not allowed,
|
|
224
|
+
// and microtasks still run inside the render cycle — defer with a macrotask instead.
|
|
225
|
+
setTimeout(() => {
|
|
226
|
+
try {
|
|
227
|
+
root.unmount();
|
|
228
|
+
} catch (error) {
|
|
229
|
+
logger.error('Error unmounting React root in Grid Pro cell renderer:', error);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -7,10 +7,21 @@
|
|
|
7
7
|
* - `createReactRenderer` — wraps a React component as a `RendererEntry` for use with
|
|
8
8
|
* `foundation-forms` `additionalRenderers`. Use this instead of writing raw FAST templates
|
|
9
9
|
* when your custom form renderer is authored in React/JSX.
|
|
10
|
+
* - `createGridProCellRenderer` — wraps a React component as an AG Grid cell renderer
|
|
11
|
+
* component class for use with `grid-pro` (register via `gridOptions.components` or the
|
|
12
|
+
* grid's `gridComponents` property).
|
|
10
13
|
* - `reactFactory` / `reactFactoryWithProvider` — mount React component trees into
|
|
11
14
|
* Genesis Foundation layout regions.
|
|
12
15
|
*/
|
|
13
16
|
|
|
14
17
|
export { reactFactory, reactFactoryWithProvider } from './react-layout-factory';
|
|
15
18
|
export { createReactRenderer } from './create-react-renderer';
|
|
19
|
+
export { createGridProCellRenderer } from './create-grid-pro-cell-renderer';
|
|
20
|
+
export { createGridProCellPortals } from './create-grid-pro-cell-portals';
|
|
21
|
+
export type { GridProCellPortals } from './create-grid-pro-cell-portals';
|
|
22
|
+
export type {
|
|
23
|
+
GridProCellRendererOptions,
|
|
24
|
+
GridProCellRendererProps,
|
|
25
|
+
GridProReactCellRenderer,
|
|
26
|
+
} from './create-grid-pro-cell-renderer';
|
|
16
27
|
export type { RendererControlProps } from '@genesislcap/foundation-forms';
|