@genesislcap/foundation-react-utils 14.416.1-alpha-c3adbe7.0 → 14.417.0-FUI-0-react-renderers.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 +68 -0
- package/dist/dts/create-react-renderer.d.ts +84 -0
- package/dist/dts/create-react-renderer.d.ts.map +1 -0
- package/dist/dts/index.d.ts +2 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/esm/create-react-renderer.js +99 -0
- package/dist/esm/index.js +1 -0
- package/dist/foundation-react-utils.api.json +112 -0
- package/dist/foundation-react-utils.d.ts +90 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +15 -11
- package/src/create-react-renderer.ts +146 -0
- package/src/index.ts +2 -0
|
@@ -2,6 +2,58 @@
|
|
|
2
2
|
"schemaVersion": "1.0.0",
|
|
3
3
|
"readme": "",
|
|
4
4
|
"modules": [
|
|
5
|
+
{
|
|
6
|
+
"kind": "javascript-module",
|
|
7
|
+
"path": "src/create-react-renderer.ts",
|
|
8
|
+
"declarations": [
|
|
9
|
+
{
|
|
10
|
+
"kind": "function",
|
|
11
|
+
"name": "createReactRenderer",
|
|
12
|
+
"return": {
|
|
13
|
+
"type": {
|
|
14
|
+
"text": "RendererEntry"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"parameters": [
|
|
18
|
+
{
|
|
19
|
+
"name": "Component",
|
|
20
|
+
"type": {
|
|
21
|
+
"text": "ComponentType<RendererControlProps>"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "options",
|
|
26
|
+
"type": {
|
|
27
|
+
"text": "{\n /**\n * The custom element tag name to register. Must be unique and contain a hyphen.\n * e.g. 'my-price-renderer'\n */\n name: string;\n /**\n * RankedTester from @jsonforms/core — determines when this renderer applies.\n * Use rankWith(rank, tester) — rank 5+ recommended to override built-ins.\n */\n tester: RankedTester;\n /**\n * Custom mapper if you need to transform state differently.\n * Defaults to mapStateToControlProps from @jsonforms/core.\n */\n mapper?: (state: JsonFormsState, ownProps: OwnPropsOfControl) => StatePropsOfControl;\n /**\n * Whether to wrap the React component in the platform's <control-wrapper>,\n * which provides consistent label rendering, error display, and accessibility.\n * Defaults to true. Set to false if your React component handles its own labels/errors.\n *\n * Note: if your component has layout conflicts with control-wrapper's internal flex styles\n * (e.g. a block-level combobox that shrinks to content width), prefer keeping this true\n * and overriding via the exposed CSS part instead:\n *\n * control-wrapper::part(wrapper) { display: block; }\n *\n * This avoids losing built-in label/error/accessibility while still allowing external styling.\n * The `part=\"wrapper\"` is exposed on the inner slot-hosting div of <control-wrapper>.\n */\n wrapWithControlWrapper?: boolean;\n }"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"description": "Converts a React component into a `RendererEntry` for use with `foundation-form`'s\n`additionalRenderers` property.\n\nThe component is registered as a custom element via `r2wc` and wrapped in a FAST\n`html` template so it integrates with the JSON Forms dispatch renderer.\n\n**Important — use platform web components for inputs, not native HTML elements.**\nThe React component runs inside an `r2wc` custom element inside a FAST template,\ncreating a three-layer shadow DOM boundary. React's synthetic event system does not\ncross shadow boundaries, so `onChange` on a plain `<input>` will never fire.\nUse `rapid-number-field`, `rapid-text-field`, etc. and attach listeners via\n`addEventListener` in a `useEffect` instead.",
|
|
32
|
+
"privacy": "public"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"kind": "variable",
|
|
36
|
+
"name": "WebComponent"
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"exports": [
|
|
40
|
+
{
|
|
41
|
+
"kind": "js",
|
|
42
|
+
"name": "createReactRenderer",
|
|
43
|
+
"declaration": {
|
|
44
|
+
"name": "createReactRenderer",
|
|
45
|
+
"module": "src/create-react-renderer.ts"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"kind": "custom-element-definition",
|
|
50
|
+
"declaration": {
|
|
51
|
+
"name": "WebComponent",
|
|
52
|
+
"module": "src/create-react-renderer.ts"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
},
|
|
5
57
|
{
|
|
6
58
|
"kind": "javascript-module",
|
|
7
59
|
"path": "src/index.ts",
|
|
@@ -22,6 +74,22 @@
|
|
|
22
74
|
"name": "reactFactoryWithProvider",
|
|
23
75
|
"module": "./react-layout-factory"
|
|
24
76
|
}
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"kind": "js",
|
|
80
|
+
"name": "createReactRenderer",
|
|
81
|
+
"declaration": {
|
|
82
|
+
"name": "createReactRenderer",
|
|
83
|
+
"module": "./create-react-renderer"
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"kind": "js",
|
|
88
|
+
"name": "RendererControlProps",
|
|
89
|
+
"declaration": {
|
|
90
|
+
"name": "RendererControlProps",
|
|
91
|
+
"package": "@genesislcap/foundation-forms"
|
|
92
|
+
}
|
|
25
93
|
}
|
|
26
94
|
]
|
|
27
95
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { RendererControlProps, RendererEntry } from '@genesislcap/foundation-forms';
|
|
2
|
+
import { type JsonFormsState, type OwnPropsOfControl, type RankedTester, type StatePropsOfControl } from '@jsonforms/core';
|
|
3
|
+
import type { ComponentType } from 'react';
|
|
4
|
+
/**
|
|
5
|
+
* Converts a React component into a `RendererEntry` for use with `foundation-form`'s
|
|
6
|
+
* `additionalRenderers` property.
|
|
7
|
+
*
|
|
8
|
+
* The component is registered as a custom element via `r2wc` and wrapped in a FAST
|
|
9
|
+
* `html` template so it integrates with the JSON Forms dispatch renderer.
|
|
10
|
+
*
|
|
11
|
+
* **Important — use platform web components for inputs, not native HTML elements.**
|
|
12
|
+
* The React component runs inside an `r2wc` custom element inside a FAST template,
|
|
13
|
+
* creating a three-layer shadow DOM boundary. React's synthetic event system does not
|
|
14
|
+
* cross shadow boundaries, so `onChange` on a plain `<input>` will never fire.
|
|
15
|
+
* Use `rapid-number-field`, `rapid-text-field`, etc. and attach listeners via
|
|
16
|
+
* `addEventListener` in a `useEffect` instead.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* import { useEffect, useRef } from 'react';
|
|
21
|
+
* import { createReactRenderer, type RendererControlProps } from '@genesislcap/foundation-react-utils';
|
|
22
|
+
* import { isNumberControl, rankWith } from '@jsonforms/core';
|
|
23
|
+
*
|
|
24
|
+
* function PriceRenderer({ data, path, enabled, handleChange }: RendererControlProps) {
|
|
25
|
+
* const ref = useRef<HTMLElement>(null);
|
|
26
|
+
*
|
|
27
|
+
* useEffect(() => {
|
|
28
|
+
* const el = ref.current as any;
|
|
29
|
+
* if (!el) return;
|
|
30
|
+
* const onChange = (e: CustomEvent) => {
|
|
31
|
+
* const val = (e.target as any).value;
|
|
32
|
+
* handleChange(path, val === '' ? undefined : Number(val));
|
|
33
|
+
* };
|
|
34
|
+
* el.addEventListener('change', onChange);
|
|
35
|
+
* return () => el.removeEventListener('change', onChange);
|
|
36
|
+
* }, [path, handleChange]);
|
|
37
|
+
*
|
|
38
|
+
* return <rapid-number-field ref={ref} value={data ?? ''} disabled={!enabled || undefined} />;
|
|
39
|
+
* }
|
|
40
|
+
*
|
|
41
|
+
* export const priceRendererEntry = createReactRenderer(PriceRenderer, {
|
|
42
|
+
* name: 'my-price-renderer',
|
|
43
|
+
* tester: rankWith(6, isNumberControl),
|
|
44
|
+
* });
|
|
45
|
+
*
|
|
46
|
+
* // On the form element:
|
|
47
|
+
* // form.additionalRenderers = [priceRendererEntry];
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* @public
|
|
51
|
+
*/
|
|
52
|
+
export declare function createReactRenderer(Component: ComponentType<RendererControlProps>, options: {
|
|
53
|
+
/**
|
|
54
|
+
* The custom element tag name to register. Must be unique and contain a hyphen.
|
|
55
|
+
* e.g. 'my-price-renderer'
|
|
56
|
+
*/
|
|
57
|
+
name: string;
|
|
58
|
+
/**
|
|
59
|
+
* RankedTester from @jsonforms/core — determines when this renderer applies.
|
|
60
|
+
* Use rankWith(rank, tester) — rank 5+ recommended to override built-ins.
|
|
61
|
+
*/
|
|
62
|
+
tester: RankedTester;
|
|
63
|
+
/**
|
|
64
|
+
* Custom mapper if you need to transform state differently.
|
|
65
|
+
* Defaults to mapStateToControlProps from @jsonforms/core.
|
|
66
|
+
*/
|
|
67
|
+
mapper?: (state: JsonFormsState, ownProps: OwnPropsOfControl) => StatePropsOfControl;
|
|
68
|
+
/**
|
|
69
|
+
* Whether to wrap the React component in the platform's <control-wrapper>,
|
|
70
|
+
* which provides consistent label rendering, error display, and accessibility.
|
|
71
|
+
* Defaults to true. Set to false if your React component handles its own labels/errors.
|
|
72
|
+
*
|
|
73
|
+
* Note: if your component has layout conflicts with control-wrapper's internal flex styles
|
|
74
|
+
* (e.g. a block-level combobox that shrinks to content width), prefer keeping this true
|
|
75
|
+
* and overriding via the exposed CSS part instead:
|
|
76
|
+
*
|
|
77
|
+
* control-wrapper::part(wrapper) { display: block; }
|
|
78
|
+
*
|
|
79
|
+
* This avoids losing built-in label/error/accessibility while still allowing external styling.
|
|
80
|
+
* The `part="wrapper"` is exposed on the inner slot-hosting div of <control-wrapper>.
|
|
81
|
+
*/
|
|
82
|
+
wrapWithControlWrapper?: boolean;
|
|
83
|
+
}): RendererEntry;
|
|
84
|
+
//# sourceMappingURL=create-react-renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-react-renderer.d.ts","sourceRoot":"","sources":["../../src/create-react-renderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAEzF,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACzB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,aAAa,CAAC,oBAAoB,CAAC,EAC9C,OAAO,EAAE;IACP;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,MAAM,EAAE,YAAY,CAAC;IACrB;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,iBAAiB,KAAK,mBAAmB,CAAC;IACrF;;;;;;;;;;;;;OAaG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC,GACA,aAAa,CAmDf"}
|
package/dist/dts/index.d.ts
CHANGED
|
@@ -7,4 +7,6 @@
|
|
|
7
7
|
* Exports will be added here as utilities are developed.
|
|
8
8
|
*/
|
|
9
9
|
export { reactFactory, reactFactoryWithProvider } from './react-layout-factory';
|
|
10
|
+
export { createReactRenderer } from './create-react-renderer';
|
|
11
|
+
export type { RendererControlProps } from '@genesislcap/foundation-forms';
|
|
10
12
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/dts/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,YAAY,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,YAAY,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAChF,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,YAAY,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { html } from '@genesislcap/web-core';
|
|
2
|
+
import { mapStateToControlProps, } from '@jsonforms/core';
|
|
3
|
+
import r2wc from '@r2wc/react-to-web-component';
|
|
4
|
+
/**
|
|
5
|
+
* Converts a React component into a `RendererEntry` for use with `foundation-form`'s
|
|
6
|
+
* `additionalRenderers` property.
|
|
7
|
+
*
|
|
8
|
+
* The component is registered as a custom element via `r2wc` and wrapped in a FAST
|
|
9
|
+
* `html` template so it integrates with the JSON Forms dispatch renderer.
|
|
10
|
+
*
|
|
11
|
+
* **Important — use platform web components for inputs, not native HTML elements.**
|
|
12
|
+
* The React component runs inside an `r2wc` custom element inside a FAST template,
|
|
13
|
+
* creating a three-layer shadow DOM boundary. React's synthetic event system does not
|
|
14
|
+
* cross shadow boundaries, so `onChange` on a plain `<input>` will never fire.
|
|
15
|
+
* Use `rapid-number-field`, `rapid-text-field`, etc. and attach listeners via
|
|
16
|
+
* `addEventListener` in a `useEffect` instead.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* import { useEffect, useRef } from 'react';
|
|
21
|
+
* import { createReactRenderer, type RendererControlProps } from '@genesislcap/foundation-react-utils';
|
|
22
|
+
* import { isNumberControl, rankWith } from '@jsonforms/core';
|
|
23
|
+
*
|
|
24
|
+
* function PriceRenderer({ data, path, enabled, handleChange }: RendererControlProps) {
|
|
25
|
+
* const ref = useRef<HTMLElement>(null);
|
|
26
|
+
*
|
|
27
|
+
* useEffect(() => {
|
|
28
|
+
* const el = ref.current as any;
|
|
29
|
+
* if (!el) return;
|
|
30
|
+
* const onChange = (e: CustomEvent) => {
|
|
31
|
+
* const val = (e.target as any).value;
|
|
32
|
+
* handleChange(path, val === '' ? undefined : Number(val));
|
|
33
|
+
* };
|
|
34
|
+
* el.addEventListener('change', onChange);
|
|
35
|
+
* return () => el.removeEventListener('change', onChange);
|
|
36
|
+
* }, [path, handleChange]);
|
|
37
|
+
*
|
|
38
|
+
* return <rapid-number-field ref={ref} value={data ?? ''} disabled={!enabled || undefined} />;
|
|
39
|
+
* }
|
|
40
|
+
*
|
|
41
|
+
* export const priceRendererEntry = createReactRenderer(PriceRenderer, {
|
|
42
|
+
* name: 'my-price-renderer',
|
|
43
|
+
* tester: rankWith(6, isNumberControl),
|
|
44
|
+
* });
|
|
45
|
+
*
|
|
46
|
+
* // On the form element:
|
|
47
|
+
* // form.additionalRenderers = [priceRendererEntry];
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* @public
|
|
51
|
+
*/
|
|
52
|
+
export function createReactRenderer(Component, options) {
|
|
53
|
+
var _a;
|
|
54
|
+
const WebComponent = r2wc(Component, {
|
|
55
|
+
props: {
|
|
56
|
+
data: 'json',
|
|
57
|
+
path: 'string',
|
|
58
|
+
label: 'string',
|
|
59
|
+
errors: 'string',
|
|
60
|
+
enabled: 'boolean',
|
|
61
|
+
required: 'boolean',
|
|
62
|
+
handleChange: 'function',
|
|
63
|
+
uischema: 'json',
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
if (!customElements.get(options.name)) {
|
|
67
|
+
customElements.define(options.name, WebComponent);
|
|
68
|
+
}
|
|
69
|
+
const { name } = options;
|
|
70
|
+
const useWrapper = options.wrapWithControlWrapper !== false;
|
|
71
|
+
const innerTemplate = (elementName) => html `
|
|
72
|
+
<${elementName}
|
|
73
|
+
:data=${(x) => x.control.data}
|
|
74
|
+
:path=${(x) => x.control.path}
|
|
75
|
+
:label=${(x) => x.control.label}
|
|
76
|
+
:errors=${(x) => x.control.errors}
|
|
77
|
+
:enabled=${(x) => x.control.enabled}
|
|
78
|
+
:required=${(x) => x.control.required}
|
|
79
|
+
:handleChange=${(x) => x.control.handleChange}
|
|
80
|
+
:uischema=${(x) => x.control.uischema}
|
|
81
|
+
></${elementName}>
|
|
82
|
+
`;
|
|
83
|
+
const template = useWrapper
|
|
84
|
+
? html `
|
|
85
|
+
<template>
|
|
86
|
+
<control-wrapper :control=${(x) => x.control} :json-forms=${(x) => x.jsonforms}>
|
|
87
|
+
${innerTemplate(name)}
|
|
88
|
+
</control-wrapper>
|
|
89
|
+
</template>
|
|
90
|
+
`
|
|
91
|
+
: html `
|
|
92
|
+
<template>${innerTemplate(name)}</template>
|
|
93
|
+
`;
|
|
94
|
+
return {
|
|
95
|
+
renderer: template,
|
|
96
|
+
tester: options.tester,
|
|
97
|
+
mapper: (_a = options.mapper) !== null && _a !== void 0 ? _a : mapStateToControlProps,
|
|
98
|
+
};
|
|
99
|
+
}
|
package/dist/esm/index.js
CHANGED
|
@@ -172,6 +172,118 @@
|
|
|
172
172
|
"name": "",
|
|
173
173
|
"preserveMemberOrder": false,
|
|
174
174
|
"members": [
|
|
175
|
+
{
|
|
176
|
+
"kind": "Function",
|
|
177
|
+
"canonicalReference": "@genesislcap/foundation-react-utils!createReactRenderer:function(1)",
|
|
178
|
+
"docComment": "/**\n * Converts a React component into a `RendererEntry` for use with `foundation-form`'s `additionalRenderers` property.\n *\n * The component is registered as a custom element via `r2wc` and wrapped in a FAST `html` template so it integrates with the JSON Forms dispatch renderer.\n *\n * **Important — use platform web components for inputs, not native HTML elements.** The React component runs inside an `r2wc` custom element inside a FAST template, creating a three-layer shadow DOM boundary. React's synthetic event system does not cross shadow boundaries, so `onChange` on a plain `<input>` will never fire. Use `rapid-number-field`, `rapid-text-field`, etc. and attach listeners via `addEventListener` in a `useEffect` instead.\n *\n * @example\n * ```tsx\n * import { useEffect, useRef } from 'react';\n * import { createReactRenderer, type RendererControlProps } from '@genesislcap/foundation-react-utils';\n * import { isNumberControl, rankWith } from '@jsonforms/core';\n *\n * function PriceRenderer({ data, path, enabled, handleChange }: RendererControlProps) {\n * const ref = useRef<HTMLElement>(null);\n *\n * useEffect(() => {\n * const el = ref.current as any;\n * if (!el) return;\n * const onChange = (e: CustomEvent) => {\n * const val = (e.target as any).value;\n * handleChange(path, val === '' ? undefined : Number(val));\n * };\n * el.addEventListener('change', onChange);\n * return () => el.removeEventListener('change', onChange);\n * }, [path, handleChange]);\n *\n * return <rapid-number-field ref={ref} value={data ?? ''} disabled={!enabled || undefined} />;\n * }\n *\n * export const priceRendererEntry = createReactRenderer(PriceRenderer, {\n * name: 'my-price-renderer',\n * tester: rankWith(6, isNumberControl),\n * });\n *\n * // On the form element:\n * // form.additionalRenderers = [priceRendererEntry];\n * ```\n *\n * @public\n */\n",
|
|
179
|
+
"excerptTokens": [
|
|
180
|
+
{
|
|
181
|
+
"kind": "Content",
|
|
182
|
+
"text": "export declare function createReactRenderer(Component: "
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"kind": "Reference",
|
|
186
|
+
"text": "ComponentType",
|
|
187
|
+
"canonicalReference": "@types/react!React.ComponentType:type"
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"kind": "Content",
|
|
191
|
+
"text": "<"
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"kind": "Reference",
|
|
195
|
+
"text": "RendererControlProps",
|
|
196
|
+
"canonicalReference": "@genesislcap/foundation-forms!RendererControlProps:interface"
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"kind": "Content",
|
|
200
|
+
"text": ">"
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
"kind": "Content",
|
|
204
|
+
"text": ", options: "
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"kind": "Content",
|
|
208
|
+
"text": "{\n name: string;\n tester: "
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
"kind": "Reference",
|
|
212
|
+
"text": "RankedTester",
|
|
213
|
+
"canonicalReference": "@jsonforms/core!RankedTester:type"
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"kind": "Content",
|
|
217
|
+
"text": ";\n mapper?: (state: "
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"kind": "Reference",
|
|
221
|
+
"text": "JsonFormsState",
|
|
222
|
+
"canonicalReference": "@jsonforms/core!JsonFormsState:interface"
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
"kind": "Content",
|
|
226
|
+
"text": ", ownProps: "
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"kind": "Reference",
|
|
230
|
+
"text": "OwnPropsOfControl",
|
|
231
|
+
"canonicalReference": "@jsonforms/core!OwnPropsOfControl:interface"
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
"kind": "Content",
|
|
235
|
+
"text": ") => "
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
"kind": "Reference",
|
|
239
|
+
"text": "StatePropsOfControl",
|
|
240
|
+
"canonicalReference": "@jsonforms/core!StatePropsOfControl:interface"
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
"kind": "Content",
|
|
244
|
+
"text": ";\n wrapWithControlWrapper?: boolean;\n}"
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
"kind": "Content",
|
|
248
|
+
"text": "): "
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
"kind": "Reference",
|
|
252
|
+
"text": "RendererEntry",
|
|
253
|
+
"canonicalReference": "@genesislcap/foundation-forms!RendererEntry:type"
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
"kind": "Content",
|
|
257
|
+
"text": ";"
|
|
258
|
+
}
|
|
259
|
+
],
|
|
260
|
+
"fileUrlPath": "src/create-react-renderer.ts",
|
|
261
|
+
"returnTypeTokenRange": {
|
|
262
|
+
"startIndex": 16,
|
|
263
|
+
"endIndex": 17
|
|
264
|
+
},
|
|
265
|
+
"releaseTag": "Public",
|
|
266
|
+
"overloadIndex": 1,
|
|
267
|
+
"parameters": [
|
|
268
|
+
{
|
|
269
|
+
"parameterName": "Component",
|
|
270
|
+
"parameterTypeTokenRange": {
|
|
271
|
+
"startIndex": 1,
|
|
272
|
+
"endIndex": 5
|
|
273
|
+
},
|
|
274
|
+
"isOptional": false
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
"parameterName": "options",
|
|
278
|
+
"parameterTypeTokenRange": {
|
|
279
|
+
"startIndex": 6,
|
|
280
|
+
"endIndex": 15
|
|
281
|
+
},
|
|
282
|
+
"isOptional": false
|
|
283
|
+
}
|
|
284
|
+
],
|
|
285
|
+
"name": "createReactRenderer"
|
|
286
|
+
},
|
|
175
287
|
{
|
|
176
288
|
"kind": "Function",
|
|
177
289
|
"canonicalReference": "@genesislcap/foundation-react-utils!reactFactory:function(1)",
|
|
@@ -1,5 +1,93 @@
|
|
|
1
1
|
import type { ComponentFactory } from '@genesislcap/foundation-layout';
|
|
2
|
+
import type { ComponentType } from 'react';
|
|
3
|
+
import { JsonFormsState } from '@jsonforms/core';
|
|
4
|
+
import { OwnPropsOfControl } from '@jsonforms/core';
|
|
5
|
+
import { RankedTester } from '@jsonforms/core';
|
|
2
6
|
import * as React_2 from 'react';
|
|
7
|
+
import { RendererControlProps } from '@genesislcap/foundation-forms';
|
|
8
|
+
import type { RendererEntry } from '@genesislcap/foundation-forms';
|
|
9
|
+
import { StatePropsOfControl } from '@jsonforms/core';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Converts a React component into a `RendererEntry` for use with `foundation-form`'s
|
|
13
|
+
* `additionalRenderers` property.
|
|
14
|
+
*
|
|
15
|
+
* The component is registered as a custom element via `r2wc` and wrapped in a FAST
|
|
16
|
+
* `html` template so it integrates with the JSON Forms dispatch renderer.
|
|
17
|
+
*
|
|
18
|
+
* **Important — use platform web components for inputs, not native HTML elements.**
|
|
19
|
+
* The React component runs inside an `r2wc` custom element inside a FAST template,
|
|
20
|
+
* creating a three-layer shadow DOM boundary. React's synthetic event system does not
|
|
21
|
+
* cross shadow boundaries, so `onChange` on a plain `<input>` will never fire.
|
|
22
|
+
* Use `rapid-number-field`, `rapid-text-field`, etc. and attach listeners via
|
|
23
|
+
* `addEventListener` in a `useEffect` instead.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```tsx
|
|
27
|
+
* import { useEffect, useRef } from 'react';
|
|
28
|
+
* import { createReactRenderer, type RendererControlProps } from '@genesislcap/foundation-react-utils';
|
|
29
|
+
* import { isNumberControl, rankWith } from '@jsonforms/core';
|
|
30
|
+
*
|
|
31
|
+
* function PriceRenderer({ data, path, enabled, handleChange }: RendererControlProps) {
|
|
32
|
+
* const ref = useRef<HTMLElement>(null);
|
|
33
|
+
*
|
|
34
|
+
* useEffect(() => {
|
|
35
|
+
* const el = ref.current as any;
|
|
36
|
+
* if (!el) return;
|
|
37
|
+
* const onChange = (e: CustomEvent) => {
|
|
38
|
+
* const val = (e.target as any).value;
|
|
39
|
+
* handleChange(path, val === '' ? undefined : Number(val));
|
|
40
|
+
* };
|
|
41
|
+
* el.addEventListener('change', onChange);
|
|
42
|
+
* return () => el.removeEventListener('change', onChange);
|
|
43
|
+
* }, [path, handleChange]);
|
|
44
|
+
*
|
|
45
|
+
* return <rapid-number-field ref={ref} value={data ?? ''} disabled={!enabled || undefined} />;
|
|
46
|
+
* }
|
|
47
|
+
*
|
|
48
|
+
* export const priceRendererEntry = createReactRenderer(PriceRenderer, {
|
|
49
|
+
* name: 'my-price-renderer',
|
|
50
|
+
* tester: rankWith(6, isNumberControl),
|
|
51
|
+
* });
|
|
52
|
+
*
|
|
53
|
+
* // On the form element:
|
|
54
|
+
* // form.additionalRenderers = [priceRendererEntry];
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* @public
|
|
58
|
+
*/
|
|
59
|
+
export declare function createReactRenderer(Component: ComponentType<RendererControlProps>, options: {
|
|
60
|
+
/**
|
|
61
|
+
* The custom element tag name to register. Must be unique and contain a hyphen.
|
|
62
|
+
* e.g. 'my-price-renderer'
|
|
63
|
+
*/
|
|
64
|
+
name: string;
|
|
65
|
+
/**
|
|
66
|
+
* RankedTester from @jsonforms/core — determines when this renderer applies.
|
|
67
|
+
* Use rankWith(rank, tester) — rank 5+ recommended to override built-ins.
|
|
68
|
+
*/
|
|
69
|
+
tester: RankedTester;
|
|
70
|
+
/**
|
|
71
|
+
* Custom mapper if you need to transform state differently.
|
|
72
|
+
* Defaults to mapStateToControlProps from @jsonforms/core.
|
|
73
|
+
*/
|
|
74
|
+
mapper?: (state: JsonFormsState, ownProps: OwnPropsOfControl) => StatePropsOfControl;
|
|
75
|
+
/**
|
|
76
|
+
* Whether to wrap the React component in the platform's <control-wrapper>,
|
|
77
|
+
* which provides consistent label rendering, error display, and accessibility.
|
|
78
|
+
* Defaults to true. Set to false if your React component handles its own labels/errors.
|
|
79
|
+
*
|
|
80
|
+
* Note: if your component has layout conflicts with control-wrapper's internal flex styles
|
|
81
|
+
* (e.g. a block-level combobox that shrinks to content width), prefer keeping this true
|
|
82
|
+
* and overriding via the exposed CSS part instead:
|
|
83
|
+
*
|
|
84
|
+
* control-wrapper::part(wrapper) { display: block; }
|
|
85
|
+
*
|
|
86
|
+
* This avoids losing built-in label/error/accessibility while still allowing external styling.
|
|
87
|
+
* The `part="wrapper"` is exposed on the inner slot-hosting div of <control-wrapper>.
|
|
88
|
+
*/
|
|
89
|
+
wrapWithControlWrapper?: boolean;
|
|
90
|
+
}): RendererEntry;
|
|
3
91
|
|
|
4
92
|
/**
|
|
5
93
|
* Creates a factory function for rendering React components in layout items.
|
|
@@ -49,4 +137,6 @@ export declare function reactFactoryWithProvider<CP = {}, WP = {}>(Component: Re
|
|
|
49
137
|
children: React_2.ReactNode;
|
|
50
138
|
}>, wrapperProps: WP, componentProps?: CP): ComponentFactory;
|
|
51
139
|
|
|
140
|
+
export { RendererControlProps }
|
|
141
|
+
|
|
52
142
|
export { }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../src/index.ts","../src/react-layout-factory.tsx"],"version":"5.9.2"}
|
|
1
|
+
{"root":["../src/create-react-renderer.ts","../src/index.ts","../src/react-layout-factory.tsx"],"version":"5.9.2"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genesislcap/foundation-react-utils",
|
|
3
3
|
"description": "Genesis Foundation React Utils",
|
|
4
|
-
"version": "14.
|
|
4
|
+
"version": "14.417.0-FUI-0-react-renderers.1",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"license": "SEE LICENSE IN license.txt",
|
|
7
7
|
"main": "dist/esm/index.js",
|
|
@@ -29,21 +29,25 @@
|
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@genesislcap/foundation-testing": "14.
|
|
33
|
-
"@genesislcap/genx": "14.
|
|
34
|
-
"@genesislcap/rollup-builder": "14.
|
|
35
|
-
"@genesislcap/ts-builder": "14.
|
|
36
|
-
"@genesislcap/uvu-playwright-builder": "14.
|
|
37
|
-
"@genesislcap/vite-builder": "14.
|
|
38
|
-
"@genesislcap/webpack-builder": "14.
|
|
32
|
+
"@genesislcap/foundation-testing": "14.417.0-FUI-0-react-renderers.1",
|
|
33
|
+
"@genesislcap/genx": "14.417.0-FUI-0-react-renderers.1",
|
|
34
|
+
"@genesislcap/rollup-builder": "14.417.0-FUI-0-react-renderers.1",
|
|
35
|
+
"@genesislcap/ts-builder": "14.417.0-FUI-0-react-renderers.1",
|
|
36
|
+
"@genesislcap/uvu-playwright-builder": "14.417.0-FUI-0-react-renderers.1",
|
|
37
|
+
"@genesislcap/vite-builder": "14.417.0-FUI-0-react-renderers.1",
|
|
38
|
+
"@genesislcap/webpack-builder": "14.417.0-FUI-0-react-renderers.1"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"react": "^19.0.0",
|
|
42
42
|
"react-dom": "^19.0.0"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@genesislcap/foundation-
|
|
46
|
-
"@genesislcap/foundation-
|
|
45
|
+
"@genesislcap/foundation-forms": "14.417.0-FUI-0-react-renderers.1",
|
|
46
|
+
"@genesislcap/foundation-layout": "14.417.0-FUI-0-react-renderers.1",
|
|
47
|
+
"@genesislcap/foundation-logger": "14.417.0-FUI-0-react-renderers.1",
|
|
48
|
+
"@genesislcap/web-core": "14.417.0-FUI-0-react-renderers.1",
|
|
49
|
+
"@jsonforms/core": "^3.2.1",
|
|
50
|
+
"@r2wc/react-to-web-component": "^2.0.2"
|
|
47
51
|
},
|
|
48
52
|
"repository": {
|
|
49
53
|
"type": "git",
|
|
@@ -54,5 +58,5 @@
|
|
|
54
58
|
"access": "public"
|
|
55
59
|
},
|
|
56
60
|
"customElements": "dist/custom-elements.json",
|
|
57
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "41516c5593cc64a42b33da71ea45e3e3a8beab88"
|
|
58
62
|
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { RendererControlProps, RendererEntry } from '@genesislcap/foundation-forms';
|
|
2
|
+
import { html } from '@genesislcap/web-core';
|
|
3
|
+
import {
|
|
4
|
+
mapStateToControlProps,
|
|
5
|
+
type JsonFormsState,
|
|
6
|
+
type OwnPropsOfControl,
|
|
7
|
+
type RankedTester,
|
|
8
|
+
type StatePropsOfControl,
|
|
9
|
+
} from '@jsonforms/core';
|
|
10
|
+
import r2wc from '@r2wc/react-to-web-component';
|
|
11
|
+
import type { ComponentType } from 'react';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Converts a React component into a `RendererEntry` for use with `foundation-form`'s
|
|
15
|
+
* `additionalRenderers` property.
|
|
16
|
+
*
|
|
17
|
+
* The component is registered as a custom element via `r2wc` and wrapped in a FAST
|
|
18
|
+
* `html` template so it integrates with the JSON Forms dispatch renderer.
|
|
19
|
+
*
|
|
20
|
+
* **Important — use platform web components for inputs, not native HTML elements.**
|
|
21
|
+
* The React component runs inside an `r2wc` custom element inside a FAST template,
|
|
22
|
+
* creating a three-layer shadow DOM boundary. React's synthetic event system does not
|
|
23
|
+
* cross shadow boundaries, so `onChange` on a plain `<input>` will never fire.
|
|
24
|
+
* Use `rapid-number-field`, `rapid-text-field`, etc. and attach listeners via
|
|
25
|
+
* `addEventListener` in a `useEffect` instead.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```tsx
|
|
29
|
+
* import { useEffect, useRef } from 'react';
|
|
30
|
+
* import { createReactRenderer, type RendererControlProps } from '@genesislcap/foundation-react-utils';
|
|
31
|
+
* import { isNumberControl, rankWith } from '@jsonforms/core';
|
|
32
|
+
*
|
|
33
|
+
* function PriceRenderer({ data, path, enabled, handleChange }: RendererControlProps) {
|
|
34
|
+
* const ref = useRef<HTMLElement>(null);
|
|
35
|
+
*
|
|
36
|
+
* useEffect(() => {
|
|
37
|
+
* const el = ref.current as any;
|
|
38
|
+
* if (!el) return;
|
|
39
|
+
* const onChange = (e: CustomEvent) => {
|
|
40
|
+
* const val = (e.target as any).value;
|
|
41
|
+
* handleChange(path, val === '' ? undefined : Number(val));
|
|
42
|
+
* };
|
|
43
|
+
* el.addEventListener('change', onChange);
|
|
44
|
+
* return () => el.removeEventListener('change', onChange);
|
|
45
|
+
* }, [path, handleChange]);
|
|
46
|
+
*
|
|
47
|
+
* return <rapid-number-field ref={ref} value={data ?? ''} disabled={!enabled || undefined} />;
|
|
48
|
+
* }
|
|
49
|
+
*
|
|
50
|
+
* export const priceRendererEntry = createReactRenderer(PriceRenderer, {
|
|
51
|
+
* name: 'my-price-renderer',
|
|
52
|
+
* tester: rankWith(6, isNumberControl),
|
|
53
|
+
* });
|
|
54
|
+
*
|
|
55
|
+
* // On the form element:
|
|
56
|
+
* // form.additionalRenderers = [priceRendererEntry];
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @public
|
|
60
|
+
*/
|
|
61
|
+
export function createReactRenderer(
|
|
62
|
+
Component: ComponentType<RendererControlProps>,
|
|
63
|
+
options: {
|
|
64
|
+
/**
|
|
65
|
+
* The custom element tag name to register. Must be unique and contain a hyphen.
|
|
66
|
+
* e.g. 'my-price-renderer'
|
|
67
|
+
*/
|
|
68
|
+
name: string;
|
|
69
|
+
/**
|
|
70
|
+
* RankedTester from @jsonforms/core — determines when this renderer applies.
|
|
71
|
+
* Use rankWith(rank, tester) — rank 5+ recommended to override built-ins.
|
|
72
|
+
*/
|
|
73
|
+
tester: RankedTester;
|
|
74
|
+
/**
|
|
75
|
+
* Custom mapper if you need to transform state differently.
|
|
76
|
+
* Defaults to mapStateToControlProps from @jsonforms/core.
|
|
77
|
+
*/
|
|
78
|
+
mapper?: (state: JsonFormsState, ownProps: OwnPropsOfControl) => StatePropsOfControl;
|
|
79
|
+
/**
|
|
80
|
+
* Whether to wrap the React component in the platform's <control-wrapper>,
|
|
81
|
+
* which provides consistent label rendering, error display, and accessibility.
|
|
82
|
+
* Defaults to true. Set to false if your React component handles its own labels/errors.
|
|
83
|
+
*
|
|
84
|
+
* Note: if your component has layout conflicts with control-wrapper's internal flex styles
|
|
85
|
+
* (e.g. a block-level combobox that shrinks to content width), prefer keeping this true
|
|
86
|
+
* and overriding via the exposed CSS part instead:
|
|
87
|
+
*
|
|
88
|
+
* control-wrapper::part(wrapper) { display: block; }
|
|
89
|
+
*
|
|
90
|
+
* This avoids losing built-in label/error/accessibility while still allowing external styling.
|
|
91
|
+
* The `part="wrapper"` is exposed on the inner slot-hosting div of <control-wrapper>.
|
|
92
|
+
*/
|
|
93
|
+
wrapWithControlWrapper?: boolean;
|
|
94
|
+
},
|
|
95
|
+
): RendererEntry {
|
|
96
|
+
const WebComponent = r2wc(Component, {
|
|
97
|
+
props: {
|
|
98
|
+
data: 'json',
|
|
99
|
+
path: 'string',
|
|
100
|
+
label: 'string',
|
|
101
|
+
errors: 'string',
|
|
102
|
+
enabled: 'boolean',
|
|
103
|
+
required: 'boolean',
|
|
104
|
+
handleChange: 'function',
|
|
105
|
+
uischema: 'json',
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
if (!customElements.get(options.name)) {
|
|
110
|
+
customElements.define(options.name, WebComponent);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const { name } = options;
|
|
114
|
+
const useWrapper = options.wrapWithControlWrapper !== false;
|
|
115
|
+
|
|
116
|
+
const innerTemplate = (elementName: string) => html`
|
|
117
|
+
<${elementName}
|
|
118
|
+
:data=${(x: any) => x.control.data}
|
|
119
|
+
:path=${(x: any) => x.control.path}
|
|
120
|
+
:label=${(x: any) => x.control.label}
|
|
121
|
+
:errors=${(x: any) => x.control.errors}
|
|
122
|
+
:enabled=${(x: any) => x.control.enabled}
|
|
123
|
+
:required=${(x: any) => x.control.required}
|
|
124
|
+
:handleChange=${(x: any) => x.control.handleChange}
|
|
125
|
+
:uischema=${(x: any) => x.control.uischema}
|
|
126
|
+
></${elementName}>
|
|
127
|
+
`;
|
|
128
|
+
|
|
129
|
+
const template = useWrapper
|
|
130
|
+
? html`
|
|
131
|
+
<template>
|
|
132
|
+
<control-wrapper :control=${(x: any) => x.control} :json-forms=${(x: any) => x.jsonforms}>
|
|
133
|
+
${innerTemplate(name)}
|
|
134
|
+
</control-wrapper>
|
|
135
|
+
</template>
|
|
136
|
+
`
|
|
137
|
+
: html`
|
|
138
|
+
<template>${innerTemplate(name)}</template>
|
|
139
|
+
`;
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
renderer: template,
|
|
143
|
+
tester: options.tester,
|
|
144
|
+
mapper: options.mapper ?? mapStateToControlProps,
|
|
145
|
+
};
|
|
146
|
+
}
|