@genesislcap/foundation-react-utils 14.481.1 → 14.482.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.
@@ -0,0 +1,141 @@
1
+ import { createLogger } from '@genesislcap/foundation-logger';
2
+ import { createElement } from 'react';
3
+ import { flushSync } from 'react-dom';
4
+ import { createRoot } from 'react-dom/client';
5
+ const logger = createLogger('foundation-react-utils');
6
+ /**
7
+ * Wraps a React component as an AG Grid cell renderer component class for use with
8
+ * `grid-pro` / `grid-pro-beta` (`<foundation-grid-pro>` / `<rapid-grid-pro>`).
9
+ *
10
+ * Grid Pro is a framework-agnostic Web Component, so AG Grid inside it only understands
11
+ * plain-JS component classes. This helper bridges the gap: it returns a class implementing
12
+ * `ICellRendererComp` that mounts the React component into the cell with its own React root,
13
+ * re-renders it on `refresh` (so value changes and cell flashing keep working without
14
+ * remounting), and unmounts it when the cell is destroyed.
15
+ *
16
+ * The component receives the full AG Grid `ICellRendererParams` object as props — including
17
+ * `value`, `data`, `node`, `api` and anything supplied via the column's `cellRendererParams`.
18
+ * Hooks and local state inside the component work as normal.
19
+ *
20
+ * Note on React Context/Redux: by default each cell is rendered into its own independent
21
+ * React root, so renderers do NOT inherit contexts (Redux providers, theme context, React
22
+ * Router, etc.) from the application tree hosting the grid. Either pass data/callbacks
23
+ * explicitly via `cellRendererParams`, or opt into portal mode with the `portals` option
24
+ * (see {@link GridProCellRendererOptions.portals} and `createGridProCellPortals`) — portal
25
+ * cells share the application tree and inherit its contexts.
26
+ *
27
+ * Note on `key`: props are passed by spreading the AG Grid params object, and React reserves
28
+ * `key` — a `cellRendererParams` entry named `key` is consumed as the element key and never
29
+ * reaches the component, so avoid that name. (`ref` is fine: React 19 forwards it to function
30
+ * components as a regular prop.)
31
+ *
32
+ * Note on styling: cells render inside grid-pro's shadow root, so document-level stylesheets
33
+ * (CSS files imported by your app/page) do NOT reach the rendered component. Style renderers
34
+ * with inline styles or CSS-in-JS. Design-system tokens remain usable either way — CSS custom
35
+ * properties inherit across shadow boundaries (e.g. `color: 'var(--accent-fill-rest)'`).
36
+ *
37
+ * @example
38
+ * ```tsx
39
+ * import { createGridProCellRenderer, type GridProCellRendererProps } from '@genesislcap/foundation-react-utils';
40
+ *
41
+ * function PriceCell({ value }: GridProCellRendererProps) {
42
+ * return <span style={{ color: value >= 0 ? 'green' : 'red' }}>{value}</span>;
43
+ * }
44
+ *
45
+ * const gridOptions = {
46
+ * components: {
47
+ * priceCell: createGridProCellRenderer(PriceCell),
48
+ * },
49
+ * columnDefs: [
50
+ * { field: 'price', cellRenderer: 'priceCell' },
51
+ * ],
52
+ * };
53
+ *
54
+ * // <rapid-grid-pro gridOptions={gridOptions} /> — or register via the grid's
55
+ * // `gridComponents` property to make the renderer available across all columns.
56
+ * ```
57
+ *
58
+ * @param Component - The React component to render inside the cell.
59
+ * @param options - Optional {@link GridProCellRendererOptions} to tune the wrapper behaviour.
60
+ * @returns A cell renderer component class registrable via `gridOptions.components`,
61
+ * the grid's `gridComponents` property, or directly as a column's `cellRenderer`.
62
+ *
63
+ * @public
64
+ */
65
+ export function createGridProCellRenderer(Component, options = {}) {
66
+ const fillCell = options.fillCell !== false;
67
+ const portals = options.portals;
68
+ return class ReactCellRenderer {
69
+ constructor() {
70
+ this.root = null;
71
+ this.portalId = null;
72
+ }
73
+ init(params) {
74
+ this.container = document.createElement('div');
75
+ if (fillCell) {
76
+ // Fill the cell so the React content can align itself like native renderers do.
77
+ // See GridProCellRendererOptions.fillCell for when to opt out.
78
+ this.container.style.display = 'flex';
79
+ this.container.style.alignItems = 'center';
80
+ this.container.style.height = '100%';
81
+ this.container.style.width = '100%';
82
+ }
83
+ if (portals) {
84
+ // Portal mode: the shared manager renders the component into this container as
85
+ // part of the application tree (mount commits synchronously so AG Grid measures
86
+ // real content — same reasoning as the flushSync below).
87
+ this.portalId = portals.mount(this.container, Component, params);
88
+ return;
89
+ }
90
+ this.root = createRoot(this.container);
91
+ // React 19 concurrent roots only *schedule* render(), but AG Grid inserts and can
92
+ // synchronously measure the container as soon as init/getGui return (autoHeight,
93
+ // autoSizeColumns, sizeColumnsToFit) — flush the first render so the grid measures
94
+ // real content instead of an empty div (also avoids a one-frame empty flash).
95
+ // Cell init runs from AG Grid's own (non-React) call stack, so flushing is safe
96
+ // here; if it ever runs inside a host React render, React warns and falls back to
97
+ // a deferred flush rather than breaking. refresh() intentionally stays async.
98
+ flushSync(() => {
99
+ this.root.render(createElement(Component, params));
100
+ });
101
+ }
102
+ getGui() {
103
+ return this.container;
104
+ }
105
+ refresh(params) {
106
+ var _a;
107
+ if (portals) {
108
+ if (this.portalId != null) {
109
+ portals.update(this.portalId, params);
110
+ }
111
+ return true;
112
+ }
113
+ (_a = this.root) === null || _a === void 0 ? void 0 : _a.render(createElement(Component, params));
114
+ return true;
115
+ }
116
+ destroy() {
117
+ if (portals) {
118
+ if (this.portalId != null) {
119
+ portals.remove(this.portalId);
120
+ this.portalId = null;
121
+ }
122
+ return;
123
+ }
124
+ const root = this.root;
125
+ this.root = null;
126
+ if (root) {
127
+ // AG Grid can destroy cells synchronously while React is rendering (e.g. React Router
128
+ // unmounting the page that hosts the grid). Unmounting a root mid-render is not allowed,
129
+ // and microtasks still run inside the render cycle — defer with a macrotask instead.
130
+ setTimeout(() => {
131
+ try {
132
+ root.unmount();
133
+ }
134
+ catch (error) {
135
+ logger.error('Error unmounting React root in Grid Pro cell renderer:', error);
136
+ }
137
+ });
138
+ }
139
+ }
140
+ };
141
+ }
package/dist/esm/index.js CHANGED
@@ -7,8 +7,13 @@
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
  export { reactFactory, reactFactoryWithProvider } from './react-layout-factory';
14
17
  export { createReactRenderer } from './create-react-renderer';
18
+ export { createGridProCellRenderer } from './create-grid-pro-cell-renderer';
19
+ export { createGridProCellPortals } from './create-grid-pro-cell-portals';