@sitecore-feaas/clientside 0.3.10 → 0.3.11-alpha.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/README.md +43 -0
- package/build/components/FEAASComponent.js.map +1 -1
- package/build/components/FEAASElement.js +12 -8
- package/build/components/FEAASElement.js.map +1 -1
- package/build/dom/rendering.js +16 -13
- package/build/dom/rendering.js.map +1 -1
- package/build/ui/FEAASExternal.js +31 -16
- package/build/ui/FEAASExternal.js.map +1 -1
- package/build/ui/react.js +68 -13
- package/build/ui/react.js.map +1 -1
- package/build/utils/data.js +9 -1
- package/build/utils/data.js.map +1 -1
- package/dist/browser/headless.cjs +7 -7
- package/dist/browser/headless.cjs.map +3 -3
- package/dist/browser/headless.esm.js +7 -7
- package/dist/browser/headless.esm.js.map +1 -1
- package/dist/browser/index.cjs +6 -6
- package/dist/browser/index.cjs.map +3 -3
- package/dist/browser/index.esm.js +6 -6
- package/dist/browser/index.esm.js.map +1 -1
- package/dist/browser/react.cjs +7 -7
- package/dist/browser/react.cjs.map +3 -3
- package/dist/browser/react.esm.js +7 -7
- package/dist/browser/react.esm.js.map +1 -1
- package/dist/node/headless.cjs +4 -4
- package/dist/node/headless.cjs.map +3 -3
- package/dist/node/headless.esm.js +4 -4
- package/dist/node/headless.esm.js.map +3 -3
- package/dist/node/index.cjs +4 -4
- package/dist/node/index.cjs.map +3 -3
- package/dist/node/index.esm.js +5 -5
- package/dist/node/index.esm.js.map +3 -3
- package/dist/node/react.cjs +6 -6
- package/dist/node/react.cjs.map +3 -3
- package/dist/node/react.esm.js +6 -6
- package/dist/node/react.esm.js.map +3 -3
- package/headless.cjs +4 -4
- package/headless.cjs.map +3 -3
- package/headless.js +4 -4
- package/headless.js.map +3 -3
- package/package.json +5 -1
- package/react.cjs +7 -7
- package/react.cjs.map +3 -3
- package/react.js +7 -7
- package/react.js.map +3 -3
- package/src/components/FEAASComponent.tsx +1 -0
- package/src/components/FEAASElement.tsx +15 -10
- package/src/dom/rendering.ts +16 -14
- package/src/ui/FEAASExternal.tsx +41 -17
- package/src/ui/react.tsx +90 -16
- package/src/utils/data.ts +8 -1
- package/test/FEAASComponent.react.test.tsx +99 -19
- package/test/FEAASExternal.test.tsx +2 -2
- package/test/ServerComponent.test.tsx +31 -15
- package/test/dom.test.ts +70 -0
- package/types/components/FEAASComponent.d.ts +1 -0
- package/types/components/FEAASComponent.d.ts.map +1 -1
- package/types/components/FEAASElement.d.ts.map +1 -1
- package/types/dom/rendering.d.ts.map +1 -1
- package/types/tsbuildinfo +1 -1
- package/types/ui/FEAASExternal.d.ts +13 -2
- package/types/ui/FEAASExternal.d.ts.map +1 -1
- package/types/ui/react.d.ts +19 -7
- package/types/ui/react.d.ts.map +1 -1
- package/types/ui/tsbuildinfo +1 -1
- package/types/utils/data.d.ts +1 -0
- package/types/utils/data.d.ts.map +1 -1
|
@@ -145,17 +145,21 @@ export class FEAASElement<Props = any, Payload = any> extends WebComponent {
|
|
|
145
145
|
if (typeof process != 'undefined' && process.env.TESTING) return
|
|
146
146
|
const format = args[0]
|
|
147
147
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
args
|
|
152
|
-
.slice(1)
|
|
153
|
-
.map((arg: any) => {
|
|
154
|
-
return arg && typeof arg == 'object' ? '%O' : '%s'
|
|
155
|
-
})
|
|
156
|
-
.join(' '),
|
|
157
|
-
...args.slice(1)
|
|
148
|
+
if (
|
|
149
|
+
(typeof location != 'undefined' && location?.hostname.startsWith('components-')) ||
|
|
150
|
+
localStorage['Sitecore.Components.Debug']
|
|
158
151
|
)
|
|
152
|
+
console.log(
|
|
153
|
+
format +
|
|
154
|
+
' ' +
|
|
155
|
+
args
|
|
156
|
+
.slice(1)
|
|
157
|
+
.map((arg: any) => {
|
|
158
|
+
return arg && typeof arg == 'object' ? '%O' : '%s'
|
|
159
|
+
})
|
|
160
|
+
.join(' '),
|
|
161
|
+
...args.slice(1)
|
|
162
|
+
)
|
|
159
163
|
}
|
|
160
164
|
|
|
161
165
|
/** Nothing special happens on unmount, except setting a flag */
|
|
@@ -325,6 +329,7 @@ export class FEAASElement<Props = any, Payload = any> extends WebComponent {
|
|
|
325
329
|
const { React, ReactDOM, ReactDOMClient } = (this.payload as ReactPayload) || {}
|
|
326
330
|
if (!React) throw new Error(`${this.tagName}: React is not present in payload`)
|
|
327
331
|
if (!ReactDOM) throw new Error(`${this.tagName}: ReactDOM is not present in payload`)
|
|
332
|
+
if (this.getAttribute('hydrate') == 'false') return
|
|
328
333
|
if (!this.refs.topReact && !this.forceUpdateReact) {
|
|
329
334
|
if (ReactDOMClient) {
|
|
330
335
|
this.reactRoot ||= ReactDOMClient.createRoot(this.getReactRootElement())
|
package/src/dom/rendering.ts
CHANGED
|
@@ -84,14 +84,11 @@ export function setDOMAttribute(element: HTMLElement, attribute: string, value:
|
|
|
84
84
|
value = config?.processValue(element, attribute, value)
|
|
85
85
|
}
|
|
86
86
|
var prop = attribute.replace('data-path-', '')
|
|
87
|
-
if (attribute == 'data-path-src') {
|
|
87
|
+
if (attribute == 'data-path-src' || attribute == 'data-path-alt') {
|
|
88
88
|
const picture = Array.from(element.children).find((n) => n.localName == 'picture') || element
|
|
89
89
|
target = element.localName == 'img' ? element : <HTMLElement>picture.querySelector('img, video')
|
|
90
90
|
} else if (attribute == 'data-path-href') {
|
|
91
91
|
target = element.querySelector('a') || element.closest('a')
|
|
92
|
-
} else if (attribute == 'data-path-alt') {
|
|
93
|
-
const picture = Array.from(element.children).find((n) => n.localName == 'picture') || element
|
|
94
|
-
target = picture.querySelector('img, picture')
|
|
95
92
|
} else if (attribute == 'data-path-hidden') {
|
|
96
93
|
value = !value ? true : null
|
|
97
94
|
} else if (attribute == 'data-path' || attribute == 'data-embed-html' || attribute == 'html') {
|
|
@@ -121,13 +118,22 @@ export function setDOMAttribute(element: HTMLElement, attribute: string, value:
|
|
|
121
118
|
if (typeof value == 'boolean') {
|
|
122
119
|
value = value ? '' : null
|
|
123
120
|
}
|
|
124
|
-
if (!target) return
|
|
125
121
|
|
|
126
|
-
|
|
127
|
-
(element.getAttribute('data-path-placeholder') || target[prop as keyof HTMLElement]) ?? target.getAttribute(prop)
|
|
128
|
-
)
|
|
122
|
+
if (!target) return
|
|
129
123
|
|
|
130
|
-
|
|
124
|
+
const usePlaceholder = prop == 'innerHTML' || prop == 'src'
|
|
125
|
+
if (usePlaceholder && (value == null || value === '')) {
|
|
126
|
+
var placeholder = String(
|
|
127
|
+
(element.getAttribute('data-path-placeholder') || target[prop as keyof HTMLElement]) ?? target.getAttribute(prop)
|
|
128
|
+
)
|
|
129
|
+
if (prop == 'innerHTML') {
|
|
130
|
+
target.innerHTML = placeholder
|
|
131
|
+
} else if (placeholder) {
|
|
132
|
+
target.setAttribute(prop, placeholder)
|
|
133
|
+
} else {
|
|
134
|
+
target.removeAttribute(prop)
|
|
135
|
+
}
|
|
136
|
+
} else if (value != null) {
|
|
131
137
|
if (prop == 'innerHTML') {
|
|
132
138
|
var str = String(value)
|
|
133
139
|
// sanitize html of structural text elements
|
|
@@ -154,11 +160,7 @@ export function setDOMAttribute(element: HTMLElement, attribute: string, value:
|
|
|
154
160
|
}
|
|
155
161
|
}
|
|
156
162
|
} else {
|
|
157
|
-
|
|
158
|
-
target.innerHTML = placeholder
|
|
159
|
-
} else {
|
|
160
|
-
target.removeAttribute(prop)
|
|
161
|
-
}
|
|
163
|
+
target.removeAttribute(prop)
|
|
162
164
|
}
|
|
163
165
|
}
|
|
164
166
|
|
package/src/ui/FEAASExternal.tsx
CHANGED
|
@@ -22,7 +22,7 @@ const addTitles = (properties: { [key: string]: any }) =>
|
|
|
22
22
|
}, {})
|
|
23
23
|
|
|
24
24
|
/** Converts the keys of an object to kebab case. */
|
|
25
|
-
const objectKeysToKebabCase = (object: { [key: string]: any } = {}) =>
|
|
25
|
+
export const objectKeysToKebabCase = (object: { [key: string]: any } = {}) =>
|
|
26
26
|
Object.keys(object).reduce((acc: { [key: string]: any }, key) => {
|
|
27
27
|
acc[kebabCase(key)] = object[key]
|
|
28
28
|
return acc
|
|
@@ -61,38 +61,52 @@ const transformUiSchema = (uiSchema: UISchema, properties: { [key: string]: any
|
|
|
61
61
|
|
|
62
62
|
return transformed
|
|
63
63
|
}
|
|
64
|
+
|
|
64
65
|
/** Parses a property value based on its property name and type defined in the JSON properties object. */
|
|
65
|
-
export const parseProps = (propName: string,
|
|
66
|
-
//@ts-ignore
|
|
66
|
+
export const parseProps = (propName: string, value: string, properties: JSONSchema['properties']) => {
|
|
67
67
|
switch (properties?.[propName]?.type) {
|
|
68
68
|
case 'string':
|
|
69
|
-
return
|
|
69
|
+
return value
|
|
70
70
|
case 'object':
|
|
71
|
-
|
|
71
|
+
try {
|
|
72
|
+
return typeof value == 'object' && value != null ? value : JSON.parse(value)
|
|
73
|
+
} catch (e) {
|
|
74
|
+
return null
|
|
75
|
+
}
|
|
72
76
|
case 'array':
|
|
73
|
-
|
|
77
|
+
try {
|
|
78
|
+
return Array.isArray(value) ? value : JSON.parse(value)
|
|
79
|
+
} catch (e) {
|
|
80
|
+
return null
|
|
81
|
+
}
|
|
74
82
|
case 'number':
|
|
75
|
-
return
|
|
83
|
+
return parseFloat(value)
|
|
76
84
|
case 'integer':
|
|
77
|
-
return
|
|
85
|
+
return parseInt(value)
|
|
78
86
|
case 'boolean':
|
|
79
|
-
return
|
|
87
|
+
return value == 'true' || value == '1'
|
|
80
88
|
default:
|
|
81
|
-
return
|
|
89
|
+
return value
|
|
82
90
|
}
|
|
83
91
|
}
|
|
84
92
|
|
|
85
93
|
export function FEAASExternalProps(element: FEAASElement): any {
|
|
86
|
-
const id = element.getAttribute('data-external-id')
|
|
87
|
-
const schema: JSONSchema = FEAASExternal.registered[id]?.schema
|
|
88
94
|
const props = Array.from(element.attributes).reduce((prev, { name, value }) => {
|
|
95
|
+
return Object.assign(prev, { [name]: value })
|
|
96
|
+
}, {} as any)
|
|
97
|
+
return FEAASExternalPropsObject(props['data-external-id'], props)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function FEAASExternalPropsObject(id: string, props: Record<string, string>): any {
|
|
101
|
+
const schema: JSONSchema = FEAASExternal.registered[id]?.schema
|
|
102
|
+
return Object.keys(props).reduce((prev, name) => {
|
|
103
|
+
const value = props[name]
|
|
89
104
|
if (!name.startsWith('data-attribute') && !['class', 'id', 'contenteditable'].includes(name)) {
|
|
90
105
|
return { ...prev, [reverseKebabCase(name)]: parseProps(reverseKebabCase(name), value, schema?.properties) }
|
|
91
106
|
} else {
|
|
92
107
|
return prev
|
|
93
108
|
}
|
|
94
109
|
}, {})
|
|
95
|
-
return props
|
|
96
110
|
}
|
|
97
111
|
|
|
98
112
|
export type RegisteredComponents = {
|
|
@@ -110,7 +124,7 @@ export default class FEAASExternal extends FEAASElement<FEAASExternalProps, Reac
|
|
|
110
124
|
update() {
|
|
111
125
|
const { dataExternalId: id } = this.props
|
|
112
126
|
const Component = FEAASExternal.registered[id]?.component
|
|
113
|
-
if (!Component && this.
|
|
127
|
+
if (!Component && this.getAttribute('hydrate') == 'false') return
|
|
114
128
|
return super.update()
|
|
115
129
|
}
|
|
116
130
|
|
|
@@ -118,7 +132,7 @@ export default class FEAASExternal extends FEAASElement<FEAASExternalProps, Reac
|
|
|
118
132
|
const { dataExternalId: id } = props
|
|
119
133
|
const Component = FEAASExternal.registered[id]?.component
|
|
120
134
|
if (!Component) {
|
|
121
|
-
console.warn(
|
|
135
|
+
console.warn(`FEAASExternal: \`${id}\` is not registered`)
|
|
122
136
|
}
|
|
123
137
|
return <>{Component && <Component {...props} />}</>
|
|
124
138
|
}
|
|
@@ -178,9 +192,15 @@ export default class FEAASExternal extends FEAASElement<FEAASExternalProps, Reac
|
|
|
178
192
|
* })
|
|
179
193
|
*
|
|
180
194
|
*/
|
|
181
|
-
static registerComponent(
|
|
195
|
+
static registerComponent(
|
|
196
|
+
component: React.ComponentType | ((...args: any[]) => Promise<React.ReactElement>),
|
|
197
|
+
options: ExternalComponentOptions
|
|
198
|
+
) {
|
|
182
199
|
if (!options.name)
|
|
183
|
-
throw new Error(
|
|
200
|
+
throw new Error(
|
|
201
|
+
'Could not register external component. Please make sure you provide a name in the options' +
|
|
202
|
+
JSON.stringify(options)
|
|
203
|
+
)
|
|
184
204
|
const id = options.name
|
|
185
205
|
const { thumbnail, group, ui: uiSchema, ...schemaOptions } = options
|
|
186
206
|
const schema = { ...schemaOptions, type: 'object' } as JSONSchema
|
|
@@ -258,6 +278,10 @@ declare global {
|
|
|
258
278
|
dangerouslySetInnerHTML?: { __html: string }
|
|
259
279
|
[key: string]: any
|
|
260
280
|
}
|
|
281
|
+
'feaas-registration': {
|
|
282
|
+
suppressHydrationWarning?: boolean
|
|
283
|
+
components: string
|
|
284
|
+
}
|
|
261
285
|
}
|
|
262
286
|
}
|
|
263
287
|
}
|
package/src/ui/react.tsx
CHANGED
|
@@ -4,22 +4,31 @@ import {
|
|
|
4
4
|
fetchComponent,
|
|
5
5
|
getComponentURL,
|
|
6
6
|
getStylesheetURL,
|
|
7
|
-
parseComponentSource
|
|
7
|
+
parseComponentSource,
|
|
8
|
+
FEAASStylesheetProps
|
|
8
9
|
} from './index.js'
|
|
9
10
|
import React from 'react'
|
|
10
11
|
import { renderHTMLContent, HTMLNode, parseHTMLContent } from '../dom/html.js'
|
|
11
12
|
import { DataSettings } from '../utils/settings.js'
|
|
13
|
+
import { External, FEAASExternalPropsObject, getComponent, objectKeysToKebabCase } from './FEAASExternal.js'
|
|
14
|
+
import { reverseKebabCase } from '../utils/string.js'
|
|
12
15
|
export { renderHTMLContent }
|
|
13
|
-
|
|
14
|
-
export { External, linkComponents, registerComponent, getComponent } from './FEAASExternal.js'
|
|
16
|
+
export { External, linkComponents, registerComponent, getComponent, FEAASExternalPropsObject } from './FEAASExternal.js'
|
|
15
17
|
export * from './index.js'
|
|
16
18
|
export * from '../headless.js'
|
|
17
19
|
|
|
20
|
+
export type FEAASReactExternalComponentProps = { componentName: string; fallback?: any; [prop: string]: any }
|
|
21
|
+
|
|
18
22
|
export type FEAASReactComponentProps = FEAASComponentProps & {
|
|
19
23
|
preload?: boolean
|
|
20
24
|
lastModified?: Date | string
|
|
21
25
|
}
|
|
22
|
-
|
|
26
|
+
|
|
27
|
+
export const FEAASReactStylesheet = (props: FEAASStylesheetProps) => {
|
|
28
|
+
return <feaas-stylesheet {...props} />
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const FEAASReactServerComponent = async ({ ...props }: FEAASReactComponentProps) => {
|
|
23
32
|
const fetchedData = await DataSettings.fetch(props?.data || {})
|
|
24
33
|
|
|
25
34
|
const p: FEAASReactComponentProps = { ...props }
|
|
@@ -35,7 +44,6 @@ export const ServerComponent = async (props: FEAASReactComponentProps) => {
|
|
|
35
44
|
}
|
|
36
45
|
|
|
37
46
|
export function FEAASReactComponent({ data, ...props }: FEAASReactComponentProps) {
|
|
38
|
-
let html = ''
|
|
39
47
|
let preload = null
|
|
40
48
|
let children: null | React.ReactNode
|
|
41
49
|
|
|
@@ -43,9 +51,14 @@ export function FEAASReactComponent({ data, ...props }: FEAASReactComponentProps
|
|
|
43
51
|
children = parseHTMLContent(props.template, data || {}).map((node, index) =>
|
|
44
52
|
HTMLNodeToReact(
|
|
45
53
|
node,
|
|
46
|
-
(node) => {
|
|
47
|
-
if (node.localName == 'feaas-external'
|
|
48
|
-
|
|
54
|
+
(node, attributes) => {
|
|
55
|
+
if (node.localName == 'feaas-external') {
|
|
56
|
+
const camelized = {} as any
|
|
57
|
+
for (var prop in attributes) {
|
|
58
|
+
const key = prop == 'data-external-id' ? 'componentName' : reverseKebabCase(prop)
|
|
59
|
+
camelized[key] = attributes[prop]
|
|
60
|
+
}
|
|
61
|
+
return [FEAASExternalComponent, camelized]
|
|
49
62
|
}
|
|
50
63
|
},
|
|
51
64
|
index
|
|
@@ -70,6 +83,7 @@ export function FEAASReactComponent({ data, ...props }: FEAASReactComponentProps
|
|
|
70
83
|
suppressHydrationWarning
|
|
71
84
|
template={null}
|
|
72
85
|
data={typeof data == 'string' ? data : data ? JSON.stringify(data) : null}
|
|
86
|
+
hydrate={'false'}
|
|
73
87
|
>
|
|
74
88
|
{children}
|
|
75
89
|
</feaas-component>
|
|
@@ -77,20 +91,36 @@ export function FEAASReactComponent({ data, ...props }: FEAASReactComponentProps
|
|
|
77
91
|
)
|
|
78
92
|
}
|
|
79
93
|
|
|
80
|
-
export function
|
|
81
|
-
const
|
|
94
|
+
export function FEAASReactExternalComponent(props: FEAASReactExternalComponentProps) {
|
|
95
|
+
const { componentName, fallback, ...attributes } = props
|
|
96
|
+
const Component = getComponent(componentName)?.component
|
|
97
|
+
if ((!Component && fallback) || !componentName)
|
|
98
|
+
return (
|
|
99
|
+
<feaas-external data-external-id={componentName} suppressHydrationWarning hydrate={'false'}>
|
|
100
|
+
{fallback}
|
|
101
|
+
</feaas-external>
|
|
102
|
+
)
|
|
103
|
+
const parsed = FEAASExternalPropsObject(componentName, attributes)
|
|
104
|
+
delete parsed['suppressHydrationWarning']
|
|
82
105
|
// execute component as a function to supress hydration errors inside
|
|
83
106
|
return (
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
107
|
+
<>
|
|
108
|
+
<feaas-external
|
|
109
|
+
{...objectKeysToKebabCase(parsed)}
|
|
110
|
+
data-external-id={componentName}
|
|
111
|
+
suppressHydrationWarning
|
|
112
|
+
hydrate={'false'}
|
|
113
|
+
>
|
|
114
|
+
{Component == null ? null : <Component {...parsed} />}
|
|
115
|
+
</feaas-external>
|
|
116
|
+
</>
|
|
87
117
|
)
|
|
88
118
|
}
|
|
89
119
|
|
|
90
120
|
/** Turn HTMLNode into React tree. It accepts callback to customize the behavior */
|
|
91
121
|
export function HTMLNodeToReact(
|
|
92
122
|
node: HTMLNode,
|
|
93
|
-
callback?: (node: HTMLNode) => [tagName: any, attributes: Record<string, any>],
|
|
123
|
+
callback?: (node: HTMLNode, attributes: Record<string, any>) => [tagName: any, attributes: Record<string, any>],
|
|
94
124
|
key?: any
|
|
95
125
|
): React.ReactNode {
|
|
96
126
|
if (node.nodeType === 3) {
|
|
@@ -112,15 +142,59 @@ export function HTMLNodeToReact(
|
|
|
112
142
|
[k]: v
|
|
113
143
|
}
|
|
114
144
|
}, {})
|
|
145
|
+
} else if (name == 'class') {
|
|
146
|
+
attributes.className = value
|
|
115
147
|
} else {
|
|
116
148
|
attributes[name] = value
|
|
117
149
|
}
|
|
118
150
|
}
|
|
119
151
|
|
|
120
|
-
const reactNode = callback?.(node)
|
|
152
|
+
const reactNode = callback?.(node, attributes)
|
|
121
153
|
const reactName = reactNode?.[0] || node.nodeName.toLowerCase()
|
|
122
|
-
const reactAttributes = Object.assign({ key, suppressHydrationWarning: true },
|
|
154
|
+
const reactAttributes = Object.assign({ key, suppressHydrationWarning: true }, reactNode?.[1] || attributes)
|
|
123
155
|
return React.createElement(reactName, reactAttributes, ['img', 'br', 'input'].includes(reactName) ? null : children)
|
|
124
156
|
}
|
|
125
157
|
|
|
158
|
+
const NextComponents = new Map()
|
|
159
|
+
var NextClientsideRenderer: any = null
|
|
160
|
+
var NextDynamicFunction: any = null
|
|
161
|
+
export function FEAASNextExternalComponent(props: FEAASReactExternalComponentProps) {
|
|
162
|
+
// See if component is registered in current context
|
|
163
|
+
const { componentName, ...attributes } = props
|
|
164
|
+
const Component = getComponent(componentName)?.component
|
|
165
|
+
if (!NextComponents.get(componentName)) {
|
|
166
|
+
// Designate component for rendering on clientside
|
|
167
|
+
const Dynamic = NextDynamicFunction(() => Promise.resolve(NextClientsideRenderer), {
|
|
168
|
+
ssr: false,
|
|
169
|
+
loading: () => <FEAASReactExternalComponent {...attributes} componentName={componentName} />
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
NextComponents.set(componentName, Dynamic)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const External = NextComponents.get(componentName)
|
|
176
|
+
return <External {...props} fallback={Component ? <Component {...props} /> : null}></External>
|
|
177
|
+
}
|
|
178
|
+
export function enableNextClientsideComponents(dynamic: any, component: any) {
|
|
179
|
+
NextDynamicFunction = dynamic
|
|
180
|
+
NextClientsideRenderer = component
|
|
181
|
+
return component
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function FEAASExternalComponent(props: FEAASReactExternalComponentProps) {
|
|
185
|
+
if (Object.keys(props).length == 0) {
|
|
186
|
+
// Serializing server-side registrations to clientside
|
|
187
|
+
return (
|
|
188
|
+
<feaas-registration components={JSON.stringify(Object.values(External.registered))} suppressHydrationWarning />
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
if (NextDynamicFunction) return FEAASNextExternalComponent(props)
|
|
192
|
+
return FEAASReactExternalComponent(props)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export const ExternalComponent = FEAASExternalComponent
|
|
196
|
+
export const ReactExternalComponent = FEAASReactExternalComponent
|
|
197
|
+
export const NextExternalComponent = FEAASNextExternalComponent
|
|
126
198
|
export const Component = FEAASReactComponent
|
|
199
|
+
export const ServerComponent = FEAASReactServerComponent
|
|
200
|
+
export const Stylesheet = FEAASReactStylesheet
|
package/src/utils/data.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { JSONPath } from 'jsonpath-plus'
|
|
2
2
|
|
|
3
|
+
export function queryObjectRaw(json: any, path: string) {
|
|
4
|
+
try {
|
|
5
|
+
return JSONPath<any[]>({ path, json })
|
|
6
|
+
} catch (e) {
|
|
7
|
+
return []
|
|
8
|
+
}
|
|
9
|
+
}
|
|
3
10
|
export function queryObject(json: any, path: string) {
|
|
4
|
-
return
|
|
11
|
+
return queryObjectRaw(json, path)?.flat(Infinity) || []
|
|
5
12
|
}
|
|
6
13
|
|
|
7
14
|
export function queryScopes(scopes: any, datapath: string, singleValue = false, json: any = scopes) {
|
|
@@ -1,9 +1,32 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React, { useContext } from 'react'
|
|
2
2
|
import ReactDOM from 'react-dom'
|
|
3
3
|
import { describe, expect, it } from 'vitest'
|
|
4
|
-
import { Component } from '../src/ui/react.js'
|
|
4
|
+
import { Component, registerComponent, ExternalComponent } from '../src/ui/react.js'
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
function Hello(props: { firstName: string; telephone: string }) {
|
|
7
|
+
const context = useContext(DataContext)
|
|
8
|
+
|
|
9
|
+
return React.createElement(
|
|
10
|
+
'div',
|
|
11
|
+
{},
|
|
12
|
+
`Hello my name is ${props.firstName} (${typeof props.firstName}) and my tel is ${
|
|
13
|
+
props.telephone
|
|
14
|
+
} (${typeof props.telephone}). And context value is ${context}`
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
const helloSchema = {
|
|
18
|
+
name: 'MyTestComponent',
|
|
19
|
+
properties: {
|
|
20
|
+
firstName: {
|
|
21
|
+
type: 'string'
|
|
22
|
+
},
|
|
23
|
+
telephone: {
|
|
24
|
+
type: 'number'
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
} as const
|
|
28
|
+
|
|
29
|
+
export async function validate(react: any, html: string) {
|
|
7
30
|
const div = document.createElement('div')
|
|
8
31
|
document.body.appendChild(div)
|
|
9
32
|
const root = ReactDOM.render(react, div)
|
|
@@ -22,57 +45,59 @@ export async function test(react: any, html: string) {
|
|
|
22
45
|
expect(div2.innerHTML).toEqual(html)
|
|
23
46
|
}
|
|
24
47
|
|
|
48
|
+
const DataContext = React.createContext('DEFAULT')
|
|
49
|
+
|
|
25
50
|
describe('React', () => {
|
|
26
51
|
it('render react component', async () => {
|
|
27
|
-
await
|
|
52
|
+
await validate(
|
|
28
53
|
<section>
|
|
29
54
|
<Component src='library/test'></Component>
|
|
30
55
|
</section>,
|
|
31
|
-
'<section><feaas-component library="library" component="test" src="library/test"></feaas-component></section>'
|
|
56
|
+
'<section><feaas-component library="library" component="test" src="library/test" hydrate="false"></feaas-component></section>'
|
|
32
57
|
)
|
|
33
58
|
})
|
|
34
59
|
it('render react with empty template', async () => {
|
|
35
|
-
await
|
|
60
|
+
await validate(
|
|
36
61
|
<section>
|
|
37
62
|
<Component src='library/test' template=''></Component>
|
|
38
63
|
</section>,
|
|
39
|
-
'<section><feaas-component library="library" component="test" src="library/test"></feaas-component></section>'
|
|
64
|
+
'<section><feaas-component library="library" component="test" src="library/test" hydrate="false"></feaas-component></section>'
|
|
40
65
|
)
|
|
41
66
|
})
|
|
42
67
|
it('render react component with src and overrides', async () => {
|
|
43
|
-
await
|
|
68
|
+
await validate(
|
|
44
69
|
<section>
|
|
45
70
|
<Component src='library/test' component='test2'></Component>
|
|
46
71
|
</section>,
|
|
47
|
-
'<section><feaas-component library="library" component="test2" src="library/test"></feaas-component></section>'
|
|
72
|
+
'<section><feaas-component library="library" component="test2" src="library/test" hydrate="false"></feaas-component></section>'
|
|
48
73
|
)
|
|
49
74
|
})
|
|
50
75
|
it('render react component with extras', async () => {
|
|
51
|
-
await
|
|
76
|
+
await validate(
|
|
52
77
|
<section>
|
|
53
78
|
<Component src='library/test' version='v1'></Component>
|
|
54
79
|
</section>,
|
|
55
|
-
'<section><feaas-component library="library" component="test" version="v1" src="library/test"></feaas-component></section>'
|
|
80
|
+
'<section><feaas-component library="library" component="test" version="v1" src="library/test" hydrate="false"></feaas-component></section>'
|
|
56
81
|
)
|
|
57
82
|
})
|
|
58
83
|
it('render react template', async () => {
|
|
59
|
-
await
|
|
84
|
+
await validate(
|
|
60
85
|
<section>
|
|
61
86
|
<Component src='library/test' template={'<div data-path-src="abc">'}></Component>
|
|
62
87
|
</section>,
|
|
63
|
-
'<section><feaas-component library="library" component="test" src="library/test"><div data-path-src="abc"></div></feaas-component></section>'
|
|
88
|
+
'<section><feaas-component library="library" component="test" src="library/test" hydrate="false"><div data-path-src="abc"></div></feaas-component></section>'
|
|
64
89
|
)
|
|
65
90
|
})
|
|
66
91
|
it('render template with data and src', async () => {
|
|
67
|
-
await
|
|
92
|
+
await validate(
|
|
68
93
|
<section>
|
|
69
94
|
<Component src='library/test' template={'<div data-path-src="abc">'} data={{ abc: 321 }}></Component>
|
|
70
95
|
</section>,
|
|
71
|
-
'<section><feaas-component library="library" component="test" src="library/test" data="{"abc":321}"><div data-path-src="abc"></div></feaas-component></section>'
|
|
96
|
+
'<section><feaas-component library="library" component="test" src="library/test" data="{"abc":321}" hydrate="false"><div data-path-src="abc"></div></feaas-component></section>'
|
|
72
97
|
)
|
|
73
98
|
})
|
|
74
99
|
it('render template with data but without src', async () => {
|
|
75
|
-
|
|
100
|
+
validate(
|
|
76
101
|
<section>
|
|
77
102
|
<Component
|
|
78
103
|
library='library'
|
|
@@ -81,15 +106,70 @@ describe('React', () => {
|
|
|
81
106
|
data={{ abc: 321 }}
|
|
82
107
|
></Component>
|
|
83
108
|
</section>,
|
|
84
|
-
'<section><feaas-component library="library" component="test" data="{"abc":321}"><div data-path-src="abc"></div></feaas-component></section>'
|
|
109
|
+
'<section><feaas-component library="library" component="test" data="{"abc":321}" hydrate="false"><div data-path-src="abc"></div></feaas-component></section>'
|
|
85
110
|
)
|
|
86
111
|
})
|
|
87
112
|
it('render template with style attribute', async () => {
|
|
88
|
-
|
|
113
|
+
validate(
|
|
89
114
|
<section>
|
|
90
115
|
<Component template={'<div data-path-src="abc">'} data={{ abc: 321 }}></Component>
|
|
91
116
|
</section>,
|
|
92
|
-
'<section><feaas-component data="{"abc":321}"><div data-path-src="abc"></div></feaas-component></section>'
|
|
117
|
+
'<section><feaas-component data="{"abc":321}" hydrate="false"><div data-path-src="abc"></div></feaas-component></section>'
|
|
118
|
+
)
|
|
119
|
+
})
|
|
120
|
+
it('render unwrapped external component with stringified values', async () => {
|
|
121
|
+
registerComponent(Hello, helloSchema)
|
|
122
|
+
validate(
|
|
123
|
+
<section>
|
|
124
|
+
<ExternalComponent componentName='MyTestComponent' telephone={'12345'} firstName='Yarik' />
|
|
125
|
+
</section>,
|
|
126
|
+
'<section><feaas-external telephone="12345" first-name="Yarik" data-external-id="MyTestComponent" hydrate="false"><div>Hello my name is Yarik (string) and my tel is 12345 (number). And context value is DEFAULT</div></feaas-external></section>'
|
|
127
|
+
)
|
|
128
|
+
})
|
|
129
|
+
it('render wrapped external component with parsed values', async () => {
|
|
130
|
+
registerComponent(Hello, helloSchema)
|
|
131
|
+
validate(
|
|
132
|
+
<section>
|
|
133
|
+
<Component
|
|
134
|
+
template={
|
|
135
|
+
'<feaas-external telephone="12345" first-name="Yarik" data-external-id="MyTestComponent"></feaas-external>'
|
|
136
|
+
}
|
|
137
|
+
data={{ abc: 321 }}
|
|
138
|
+
></Component>
|
|
139
|
+
</section>,
|
|
140
|
+
'<section><feaas-component data="{"abc":321}" hydrate="false"><feaas-external telephone="12345" first-name="Yarik" data-external-id="MyTestComponent" hydrate="false"><div>Hello my name is Yarik (string) and my tel is 12345 (number). And context value is DEFAULT</div></feaas-external></feaas-component></section>'
|
|
141
|
+
)
|
|
142
|
+
})
|
|
143
|
+
it('render wrapped external component with context provider', async () => {
|
|
144
|
+
registerComponent(Hello, helloSchema)
|
|
145
|
+
validate(
|
|
146
|
+
<DataContext.Provider value='INHERITED'>
|
|
147
|
+
<section>
|
|
148
|
+
<Component
|
|
149
|
+
template={
|
|
150
|
+
'<feaas-external telephone="12345" first-name="Yarik" data-external-id="MyTestComponent"></feaas-external>'
|
|
151
|
+
}
|
|
152
|
+
data={{ abc: 321 }}
|
|
153
|
+
></Component>
|
|
154
|
+
</section>
|
|
155
|
+
</DataContext.Provider>,
|
|
156
|
+
'<section><feaas-component data="{"abc":321}" hydrate="false"><feaas-external telephone="12345" first-name="Yarik" data-external-id="MyTestComponent" hydrate="false"><div>Hello my name is Yarik (string) and my tel is 12345 (number). And context value is INHERITED</div></feaas-external></feaas-component></section>'
|
|
157
|
+
)
|
|
158
|
+
})
|
|
159
|
+
it('render wrapped external component with context provider and data mapping', async () => {
|
|
160
|
+
registerComponent(Hello, helloSchema)
|
|
161
|
+
validate(
|
|
162
|
+
<DataContext.Provider value='INHERITED'>
|
|
163
|
+
<section>
|
|
164
|
+
<Component
|
|
165
|
+
template={
|
|
166
|
+
'<feaas-external data-path-attributes="$" first-name="Yarik" data-path-attributes="$" data-external-id="MyTestComponent"></feaas-external>'
|
|
167
|
+
}
|
|
168
|
+
data={{ telephone: 321 }}
|
|
169
|
+
></Component>
|
|
170
|
+
</section>
|
|
171
|
+
</DataContext.Provider>,
|
|
172
|
+
'<section><feaas-component data="{"telephone":321}" hydrate="false"><feaas-external data-path-attributes="$" first-name="Yarik" telephone="321" data-attributes-keys="telephone" data-external-id="MyTestComponent" hydrate="false"><div>Hello my name is Yarik (string) and my tel is 321 (number). And context value is INHERITED</div></feaas-external></feaas-component></section>'
|
|
93
173
|
)
|
|
94
174
|
})
|
|
95
175
|
})
|
|
@@ -17,7 +17,7 @@ describe('External', () => {
|
|
|
17
17
|
type: 'number'
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
-
}
|
|
20
|
+
} as const
|
|
21
21
|
function Hello2(props: { firstName: string; address: string }) {
|
|
22
22
|
return React.createElement('div', {}, `Hello my name is ${props.firstName} and my address is ${props.address}`)
|
|
23
23
|
}
|
|
@@ -58,7 +58,7 @@ describe('External', () => {
|
|
|
58
58
|
type: 'object'
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
-
}
|
|
61
|
+
} as const
|
|
62
62
|
it('can register and render component', async () => {
|
|
63
63
|
Clientside.External.registerComponent(Hello, helloSchema)
|
|
64
64
|
document.body.innerHTML =
|