@sitecore-feaas/clientside 0.3.9 → 0.3.11-alpha.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.
- 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 +29 -14
- package/build/ui/FEAASExternal.js.map +1 -1
- package/build/ui/react.js +73 -15
- 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 +35 -15
- package/src/ui/react.tsx +88 -20
- package/src/utils/data.ts +8 -1
- package/test/FEAASComponent.react.test.tsx +97 -25
- package/test/FEAASExternal.test.tsx +2 -2
- package/test/ServerComponent.test.tsx +43 -13
- 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 +9 -2
- package/types/ui/FEAASExternal.d.ts.map +1 -1
- package/types/ui/react.d.ts +17 -8
- 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 = {
|
|
@@ -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
|
package/src/ui/react.tsx
CHANGED
|
@@ -4,23 +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 { 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
|
-
src?: string
|
|
20
23
|
preload?: boolean
|
|
21
24
|
lastModified?: Date | string
|
|
22
25
|
}
|
|
23
|
-
|
|
26
|
+
|
|
27
|
+
export const FEAASReactStylesheet = (props: FEAASStylesheetProps) => {
|
|
28
|
+
return <feaas-stylesheet {...props} />
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const FEAASReactServerComponent = async ({ ...props }: FEAASReactComponentProps) => {
|
|
24
32
|
const fetchedData = await DataSettings.fetch(props?.data || {})
|
|
25
33
|
|
|
26
34
|
const p: FEAASReactComponentProps = { ...props }
|
|
@@ -36,7 +44,6 @@ export const ServerComponent = async (props: FEAASComponentProps) => {
|
|
|
36
44
|
}
|
|
37
45
|
|
|
38
46
|
export function FEAASReactComponent({ data, ...props }: FEAASReactComponentProps) {
|
|
39
|
-
let html = ''
|
|
40
47
|
let preload = null
|
|
41
48
|
let children: null | React.ReactNode
|
|
42
49
|
|
|
@@ -44,10 +51,14 @@ export function FEAASReactComponent({ data, ...props }: FEAASReactComponentProps
|
|
|
44
51
|
children = parseHTMLContent(props.template, data || {}).map((node, index) =>
|
|
45
52
|
HTMLNodeToReact(
|
|
46
53
|
node,
|
|
47
|
-
(node) => {
|
|
48
|
-
if (node.localName == 'feaas-external'
|
|
49
|
-
const
|
|
50
|
-
|
|
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 [NextDynamicFunction ? FEAASNextExternalComponent : FEAASReactExternalComponent, camelized]
|
|
51
62
|
}
|
|
52
63
|
},
|
|
53
64
|
index
|
|
@@ -72,6 +83,7 @@ export function FEAASReactComponent({ data, ...props }: FEAASReactComponentProps
|
|
|
72
83
|
suppressHydrationWarning
|
|
73
84
|
template={null}
|
|
74
85
|
data={typeof data == 'string' ? data : data ? JSON.stringify(data) : null}
|
|
86
|
+
hydrate={'false'}
|
|
75
87
|
>
|
|
76
88
|
{children}
|
|
77
89
|
</feaas-component>
|
|
@@ -79,20 +91,31 @@ export function FEAASReactComponent({ data, ...props }: FEAASReactComponentProps
|
|
|
79
91
|
)
|
|
80
92
|
}
|
|
81
93
|
|
|
82
|
-
export function
|
|
83
|
-
const
|
|
94
|
+
export function FEAASReactExternalComponent(props: FEAASReactExternalComponentProps) {
|
|
95
|
+
const { componentName, fallback, ...attributes } = props
|
|
96
|
+
const Component = getComponent(componentName)?.component
|
|
97
|
+
if (!Component && fallback) return fallback
|
|
98
|
+
const parsed = FEAASExternalPropsObject(componentName, attributes)
|
|
99
|
+
delete parsed['suppressHydrationWarning']
|
|
84
100
|
// execute component as a function to supress hydration errors inside
|
|
85
101
|
return (
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
102
|
+
<>
|
|
103
|
+
<feaas-external
|
|
104
|
+
{...objectKeysToKebabCase(parsed)}
|
|
105
|
+
data-external-id={componentName}
|
|
106
|
+
suppressHydrationWarning
|
|
107
|
+
hydrate={Component ? 'false' : null}
|
|
108
|
+
>
|
|
109
|
+
{Component == null ? null : <Component {...parsed} />}
|
|
110
|
+
</feaas-external>
|
|
111
|
+
</>
|
|
89
112
|
)
|
|
90
113
|
}
|
|
91
114
|
|
|
92
115
|
/** Turn HTMLNode into React tree. It accepts callback to customize the behavior */
|
|
93
116
|
export function HTMLNodeToReact(
|
|
94
117
|
node: HTMLNode,
|
|
95
|
-
callback?: (node: HTMLNode) => [tagName: any, attributes: Record<string, any>],
|
|
118
|
+
callback?: (node: HTMLNode, attributes: Record<string, any>) => [tagName: any, attributes: Record<string, any>],
|
|
96
119
|
key?: any
|
|
97
120
|
): React.ReactNode {
|
|
98
121
|
if (node.nodeType === 3) {
|
|
@@ -100,16 +123,61 @@ export function HTMLNodeToReact(
|
|
|
100
123
|
}
|
|
101
124
|
const children = Array.from(node.childNodes).map((child, index) => HTMLNodeToReact(child, callback, index))
|
|
102
125
|
|
|
103
|
-
const attributes: { [key: string]: string } = {}
|
|
126
|
+
const attributes: { [key: string]: string | Record<string, string> } = {}
|
|
104
127
|
for (let i = 0; i < node.attributes.length; i++) {
|
|
105
128
|
const { name, value } = node.attributes[i]
|
|
106
|
-
|
|
129
|
+
if (name == 'style') {
|
|
130
|
+
attributes[name] = String(value || '')
|
|
131
|
+
.trim()
|
|
132
|
+
.split(/\s*;\s*/)
|
|
133
|
+
.reduce((object, pair) => {
|
|
134
|
+
const [k, v] = pair.split(':')
|
|
135
|
+
return {
|
|
136
|
+
...object,
|
|
137
|
+
[k]: v
|
|
138
|
+
}
|
|
139
|
+
}, {})
|
|
140
|
+
} else if (name == 'class') {
|
|
141
|
+
attributes.className = value
|
|
142
|
+
} else {
|
|
143
|
+
attributes[name] = value
|
|
144
|
+
}
|
|
107
145
|
}
|
|
108
146
|
|
|
109
|
-
const reactNode = callback?.(node)
|
|
147
|
+
const reactNode = callback?.(node, attributes)
|
|
110
148
|
const reactName = reactNode?.[0] || node.nodeName.toLowerCase()
|
|
111
|
-
const reactAttributes = Object.assign({ key, suppressHydrationWarning: true },
|
|
149
|
+
const reactAttributes = Object.assign({ key, suppressHydrationWarning: true }, reactNode?.[1] || attributes)
|
|
112
150
|
return React.createElement(reactName, reactAttributes, ['img', 'br', 'input'].includes(reactName) ? null : children)
|
|
113
151
|
}
|
|
114
152
|
|
|
153
|
+
const NextComponents = new Map()
|
|
154
|
+
var NextClientsideRenderer: any = null
|
|
155
|
+
var NextDynamicFunction: any = null
|
|
156
|
+
export function FEAASNextExternalComponent(props: FEAASReactExternalComponentProps) {
|
|
157
|
+
// See if component is registered in current context
|
|
158
|
+
const { componentName, ...attributes } = props
|
|
159
|
+
const Component = getComponent(componentName)?.component
|
|
160
|
+
if (!NextComponents.get(componentName)) {
|
|
161
|
+
// Designate component for rendering on clientside
|
|
162
|
+
const Dynamic = NextDynamicFunction(() => Promise.resolve(NextClientsideRenderer), {
|
|
163
|
+
ssr: false,
|
|
164
|
+
loading: () => <FEAASReactExternalComponent {...attributes} componentName={componentName} />
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
NextComponents.set(componentName, Dynamic)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const External = NextComponents.get(componentName)
|
|
171
|
+
return <External {...props} fallback={Component ? <Component {...props} /> : null}></External>
|
|
172
|
+
}
|
|
173
|
+
export function enableNextClientsideComponents(dynamic: any, component: any) {
|
|
174
|
+
NextDynamicFunction = dynamic
|
|
175
|
+
NextClientsideRenderer = component
|
|
176
|
+
return component
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export const ExternalComponent = FEAASReactExternalComponent
|
|
180
|
+
export const NextExternalComponent = FEAASNextExternalComponent
|
|
115
181
|
export const Component = FEAASReactComponent
|
|
182
|
+
export const ServerComponent = FEAASReactServerComponent
|
|
183
|
+
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,23 +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
|
-
it('render template with
|
|
88
|
-
|
|
112
|
+
it('render template with style attribute', async () => {
|
|
113
|
+
validate(
|
|
89
114
|
<section>
|
|
90
|
-
<Component
|
|
115
|
+
<Component template={'<div data-path-src="abc">'} data={{ abc: 321 }}></Component>
|
|
91
116
|
</section>,
|
|
92
|
-
'<section><feaas-component
|
|
117
|
+
'<section><feaas-component data="{"abc":321}" hydrate="false"><div data-path-src="abc"></div></feaas-component></section>'
|
|
93
118
|
)
|
|
94
119
|
})
|
|
95
|
-
it('render
|
|
96
|
-
|
|
120
|
+
it('render unwrapped external component with stringified values', async () => {
|
|
121
|
+
registerComponent(Hello, helloSchema)
|
|
122
|
+
validate(
|
|
97
123
|
<section>
|
|
98
|
-
<
|
|
124
|
+
<ExternalComponent componentName='MyTestComponent' telephone={'12345'} firstName='Yarik' />
|
|
99
125
|
</section>,
|
|
100
|
-
'<section><feaas-
|
|
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>'
|
|
101
173
|
)
|
|
102
174
|
})
|
|
103
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 =
|