@sitecore-feaas/clientside 0.2.1 → 0.2.2
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 +6 -6
- package/build/components/FEAASComponent.js +69 -9
- package/build/components/FEAASComponent.js.map +1 -1
- package/build/components/FEAASElement.js +3 -0
- package/build/components/FEAASElement.js.map +1 -1
- package/build/ui/FEAAS.js +5 -0
- package/build/ui/FEAAS.js.map +1 -0
- package/build/ui/FEAASContext.js +9 -6
- package/build/ui/FEAASContext.js.map +1 -1
- package/build/ui/FEAASExternal.js +66 -4
- package/build/ui/FEAASExternal.js.map +1 -1
- package/build/ui/FEAASPicker.js +4 -0
- package/build/ui/FEAASPicker.js.map +1 -1
- package/build/ui/index.js +3 -4
- package/build/ui/index.js.map +1 -1
- package/build/ui/react.js.map +1 -1
- package/build/utils/fetch.js +26 -0
- package/build/utils/fetch.js.map +1 -0
- package/dist/browser/headless.cjs +6 -6
- package/dist/browser/headless.cjs.map +4 -4
- package/dist/browser/headless.esm.js +6 -6
- package/dist/browser/headless.esm.js.map +1 -1
- package/dist/browser/index.cjs +6 -6
- package/dist/browser/index.cjs.map +4 -4
- package/dist/browser/index.esm.js +6 -6
- package/dist/browser/index.esm.js.map +1 -1
- package/dist/browser/react.cjs +6 -6
- package/dist/browser/react.cjs.map +4 -4
- package/dist/browser/react.esm.js +6 -6
- package/dist/browser/react.esm.js.map +1 -1
- package/dist/node/headless.cjs +4 -4
- package/dist/node/headless.cjs.map +4 -4
- package/dist/node/headless.esm.js +4 -4
- package/dist/node/headless.esm.js.map +4 -4
- package/dist/node/index.cjs +4 -4
- package/dist/node/index.cjs.map +4 -4
- package/dist/node/index.esm.js +4 -4
- package/dist/node/index.esm.js.map +4 -4
- package/dist/node/react.cjs +4 -4
- package/dist/node/react.cjs.map +4 -4
- package/dist/node/react.esm.js +4 -4
- package/dist/node/react.esm.js.map +4 -4
- package/headless.cjs +4 -4
- package/headless.cjs.map +4 -4
- package/headless.js +4 -4
- package/headless.js.map +4 -4
- package/package.json +2 -2
- package/react.cjs +6 -6
- package/react.cjs.map +4 -4
- package/react.js +6 -6
- package/react.js.map +4 -4
- package/scripts/build.js +1 -1
- package/src/components/FEAASComponent.tsx +92 -8
- package/src/components/FEAASElement.tsx +13 -0
- package/src/ui/FEAAS.tsx +4 -0
- package/src/ui/FEAASContext.tsx +10 -1
- package/src/ui/FEAASExternal.tsx +73 -16
- package/src/ui/FEAASPicker.tsx +5 -2
- package/src/ui/index.ts +4 -4
- package/src/ui/react.tsx +5 -0
- package/src/utils/fetch.ts +44 -0
- package/test/FEAASComponent.test.tsx +1 -0
- package/test/FEAASComponent2.test.tsx +215 -0
- package/test/FEAASContext.test.tsx +3 -3
- package/test/FEAASExternal.test.tsx +1 -8
- package/test/fetch-data.ts +55 -0
- package/types/components/FEAASComponent.d.ts +30 -0
- package/types/components/FEAASComponent.d.ts.map +1 -1
- package/types/components/FEAASElement.d.ts +9 -1
- package/types/components/FEAASElement.d.ts.map +1 -1
- package/types/tsbuildinfo +1 -1
- package/types/ui/FEAAS.d.ts +5 -0
- package/types/ui/FEAAS.d.ts.map +1 -0
- package/types/ui/FEAASContext.d.ts +8 -0
- package/types/ui/FEAASContext.d.ts.map +1 -1
- package/types/ui/FEAASExternal.d.ts +54 -3
- package/types/ui/FEAASExternal.d.ts.map +1 -1
- package/types/ui/FEAASPicker.d.ts +4 -3
- package/types/ui/FEAASPicker.d.ts.map +1 -1
- package/types/ui/index.d.ts +1 -4
- package/types/ui/index.d.ts.map +1 -1
- package/types/ui/react.d.ts.map +1 -1
- package/types/ui/tsbuildinfo +1 -1
- package/types/utils/fetch.d.ts +9 -0
- package/types/utils/fetch.d.ts.map +1 -0
package/scripts/build.js
CHANGED
|
@@ -3,6 +3,7 @@ import * as esbuild from 'esbuild'
|
|
|
3
3
|
import replace from 'esbuild-plugin-replace-regex'
|
|
4
4
|
import * as url from 'url'
|
|
5
5
|
import fs from 'fs'
|
|
6
|
+
|
|
6
7
|
const __filename = url.fileURLToPath(import.meta.url)
|
|
7
8
|
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
|
8
9
|
|
|
@@ -17,7 +18,6 @@ function compile({ input, output, ...step }) {
|
|
|
17
18
|
bundle: true,
|
|
18
19
|
write: false,
|
|
19
20
|
external: ['node:crypto', 'node-fetch', '_MAGIC_IMPORT_COMMENTS_ESBUILD_WORKAROUND__', 'react', 'react-dom', 'vm'],
|
|
20
|
-
|
|
21
21
|
...step,
|
|
22
22
|
|
|
23
23
|
minify: true,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { DEFAULT_CDN_URL, FEAASCDNComponentParams, fetchAndRevalidateComponent, parseComponentSource } from '../cdn.js'
|
|
2
2
|
import { renderDOMContent, renderDOMElement } from '../dom/rendering.js'
|
|
3
3
|
import { loadStylesheetAllowStale } from '../headless.js'
|
|
4
|
+
import { ControlledPromise } from '../utils/promise.js'
|
|
5
|
+
import { fetchData } from '../utils/fetch.js'
|
|
4
6
|
import { FEAASElement } from './FEAASElement.js'
|
|
5
7
|
|
|
6
8
|
export function FEAASComponentsProps(element: FEAASElement) {
|
|
@@ -33,8 +35,40 @@ export function FEAASComponentsProps(element: FEAASElement) {
|
|
|
33
35
|
withStylesheet: element.getAttribute('with-stylesheet') != 'false'
|
|
34
36
|
}
|
|
35
37
|
}
|
|
38
|
+
|
|
36
39
|
export type FEAASComponentsProps = ReturnType<typeof FEAASComponentsProps>
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* - Initialization lifecycle:
|
|
43
|
+
* - - `set(props)`
|
|
44
|
+
* - - `scheduleRender()` - any attribute change causes render to schedule to next frame
|
|
45
|
+
* - - `whenRendered.restart()` - rendering promise is reset and will resolve after render
|
|
46
|
+
* - - `advance()` - called on next frame
|
|
47
|
+
* - - `if (isReadyToLoad()) => boolean` - check if component has all necessary properties to load
|
|
48
|
+
* - - `load() => payload` => component is loaded
|
|
49
|
+
* - - `onLoad?(payload)` => invoke loaded callback for cached version of a component
|
|
50
|
+
* - - `advance()` => going further in state machine
|
|
51
|
+
* - - `flush()` => flushes all react changes if needed
|
|
52
|
+
* - - `update()` => component-specific method (or react) to render changes to dom
|
|
53
|
+
* - - `render(payload, props)` => render means react
|
|
54
|
+
* - - `whenRendered.resolve()` - rendering promise is resolved after rendering
|
|
55
|
+
* - - IF CACHE WAS STALE - FETCH AND RENDER AGAIN
|
|
56
|
+
* - - `fetchByPassingCache()` - load up-to-date version of a component
|
|
57
|
+
* - - `onLoad(newPayload) `
|
|
58
|
+
* - - `advance()` => going further in state machine
|
|
59
|
+
* - - `flush()` => flushes all react changes if needed
|
|
60
|
+
* - - `update()` => component-specific method (or react) to render changes to dom
|
|
61
|
+
* - - `render(payload, props)` => render means react
|
|
62
|
+
* - - `whenRendered.resolve()` - rendering promise is resolved after rendering
|
|
63
|
+
* - - `whenLoaded.resolved()` - resolved after loading of up to date component
|
|
64
|
+
*/
|
|
37
65
|
export class FEAASComponent extends FEAASElement<FEAASComponentsProps, string> {
|
|
66
|
+
readyData: any = {}
|
|
67
|
+
whenDataReady = ControlledPromise<any>(
|
|
68
|
+
(data) => (this.readyData = data),
|
|
69
|
+
(e) => this.onError(e)
|
|
70
|
+
)
|
|
71
|
+
|
|
38
72
|
static observedAttributes = [
|
|
39
73
|
'library',
|
|
40
74
|
'component',
|
|
@@ -59,6 +93,7 @@ export class FEAASComponent extends FEAASElement<FEAASComponentsProps, string> {
|
|
|
59
93
|
} as Partial<FEAASComponentsProps>
|
|
60
94
|
|
|
61
95
|
needsRefresh: boolean
|
|
96
|
+
|
|
62
97
|
setData(data: any) {
|
|
63
98
|
this.set({ data })
|
|
64
99
|
}
|
|
@@ -69,12 +104,34 @@ export class FEAASComponent extends FEAASElement<FEAASComponentsProps, string> {
|
|
|
69
104
|
|
|
70
105
|
attributeChangedCallback(name: string, oldValue: string, newValue: string): void {
|
|
71
106
|
if (oldValue === newValue) return
|
|
107
|
+
|
|
72
108
|
// force component to be refreshed
|
|
73
109
|
if (name == 'library' || name == 'component' || name == 'revision' || name == 'version') {
|
|
74
110
|
this.payload = undefined
|
|
75
111
|
}
|
|
112
|
+
|
|
76
113
|
this.needsRefresh = true
|
|
114
|
+
|
|
77
115
|
super.attributeChangedCallback(name, oldValue, newValue)
|
|
116
|
+
|
|
117
|
+
if (name === 'data') {
|
|
118
|
+
this.fetchData()
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Reset readyData and perform a new fetch.
|
|
123
|
+
async fetchData() {
|
|
124
|
+
this.readyData = undefined
|
|
125
|
+
|
|
126
|
+
this.whenDataReady = this.whenDataReady.restart()
|
|
127
|
+
|
|
128
|
+
const data = await fetchData(this.props.data)
|
|
129
|
+
const alreadyRendered = this.alreadyRendered
|
|
130
|
+
|
|
131
|
+
await this.whenDataReady.resolve(data)
|
|
132
|
+
if (alreadyRendered) {
|
|
133
|
+
this.advance()
|
|
134
|
+
}
|
|
78
135
|
}
|
|
79
136
|
|
|
80
137
|
getEditor(): any {
|
|
@@ -83,6 +140,11 @@ export class FEAASComponent extends FEAASElement<FEAASComponentsProps, string> {
|
|
|
83
140
|
|
|
84
141
|
constructor() {
|
|
85
142
|
super()
|
|
143
|
+
|
|
144
|
+
if (!this.getAttribute('data')) {
|
|
145
|
+
this.whenDataReady.resolve({})
|
|
146
|
+
}
|
|
147
|
+
|
|
86
148
|
this.addEventListener('click', () => {
|
|
87
149
|
if (this.props.editable) {
|
|
88
150
|
this.edit()
|
|
@@ -125,36 +187,58 @@ export class FEAASComponent extends FEAASElement<FEAASComponentsProps, string> {
|
|
|
125
187
|
|
|
126
188
|
async load() {
|
|
127
189
|
// defer loading to original element
|
|
128
|
-
if (this.getOriginal())
|
|
129
|
-
|
|
190
|
+
if (this.getOriginal()) return
|
|
191
|
+
|
|
192
|
+
const advanceIfEverythingIsReady = () => {
|
|
193
|
+
// html is not loaded
|
|
194
|
+
if (!this.payload) return
|
|
195
|
+
|
|
196
|
+
// shared stylesheet is still being loaded
|
|
197
|
+
if (stylesheetPromise) return
|
|
198
|
+
|
|
199
|
+
// data is still loading
|
|
200
|
+
if (!this.readyData) return
|
|
201
|
+
|
|
202
|
+
this.onLoad(this.payload)
|
|
130
203
|
}
|
|
204
|
+
|
|
131
205
|
if (this.props.withStylesheet) {
|
|
132
206
|
var stylesheetPromise = loadStylesheetAllowStale({ ...this.props, revision: 'published' }).then(() => {
|
|
133
207
|
stylesheetPromise = null
|
|
134
|
-
|
|
135
|
-
this.onLoad(this.payload)
|
|
136
|
-
}
|
|
208
|
+
advanceIfEverythingIsReady()
|
|
137
209
|
})
|
|
138
210
|
}
|
|
211
|
+
|
|
139
212
|
return Promise.all([
|
|
140
213
|
stylesheetPromise,
|
|
214
|
+
this.whenDataReady.then(() => advanceIfEverythingIsReady()),
|
|
141
215
|
fetchAndRevalidateComponent(this.props, (html) => {
|
|
142
216
|
this.payload = html
|
|
143
|
-
|
|
217
|
+
advanceIfEverythingIsReady()
|
|
144
218
|
})
|
|
145
219
|
]).then(() => this.payload)
|
|
146
220
|
}
|
|
147
221
|
|
|
222
|
+
alreadyRendered: boolean
|
|
223
|
+
|
|
224
|
+
flush() {
|
|
225
|
+
if (!this.readyData) return
|
|
226
|
+
|
|
227
|
+
super.flush()
|
|
228
|
+
}
|
|
229
|
+
|
|
148
230
|
update() {
|
|
231
|
+
this.alreadyRendered = true
|
|
232
|
+
|
|
149
233
|
if (Array.from(this.children).filter((c) => c.tagName != 'BR').length > 0 && !this.needsRefresh) {
|
|
150
|
-
renderDOMElement(this, this.
|
|
234
|
+
renderDOMElement(this, this.readyData)
|
|
151
235
|
} else {
|
|
152
236
|
this.needsRefresh = true
|
|
153
237
|
// defer rendering to original element
|
|
154
238
|
if (this.getOriginal()) {
|
|
155
239
|
return
|
|
156
240
|
}
|
|
157
|
-
renderDOMContent(this, this.payload, this.
|
|
241
|
+
renderDOMContent(this, this.payload, this.readyData)
|
|
158
242
|
}
|
|
159
243
|
}
|
|
160
244
|
}
|
|
@@ -21,6 +21,16 @@ export interface ReactPayload {
|
|
|
21
21
|
ReactDOM: typeof ReactDOM
|
|
22
22
|
ReactDOMClient: typeof ReactDOMClient
|
|
23
23
|
}
|
|
24
|
+
|
|
25
|
+
declare global {
|
|
26
|
+
interface DocumentEventMap {
|
|
27
|
+
feaasMount: Event
|
|
28
|
+
feaasUnmount: Event
|
|
29
|
+
feaasLoad: Event
|
|
30
|
+
feaasError: CustomEvent<Error>
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
24
34
|
/**
|
|
25
35
|
* FEAASElement is a superclass for FEAAS components with optional react support.
|
|
26
36
|
*
|
|
@@ -137,6 +147,8 @@ export class FEAASElement<Props = any, Payload = any> extends WebComponent {
|
|
|
137
147
|
) {
|
|
138
148
|
if (!onlyFeaas || p.tagName.startsWith('FEAAS-')) return p
|
|
139
149
|
}
|
|
150
|
+
// feaas context anywhere in the dom acts as a root element
|
|
151
|
+
if (this.tagName != 'FEAAS-CONTEXT') return document.querySelector('feaas-context')
|
|
140
152
|
}
|
|
141
153
|
|
|
142
154
|
getElements(root: HTMLElement | ShadowRoot = this.getRoot(), onlyFeaas = true): HTMLElement[] {
|
|
@@ -251,6 +263,7 @@ export class FEAASElement<Props = any, Payload = any> extends WebComponent {
|
|
|
251
263
|
} else if (this.payload == null) {
|
|
252
264
|
return
|
|
253
265
|
}
|
|
266
|
+
|
|
254
267
|
this.flush()
|
|
255
268
|
this.whenRendered.resolve(this)
|
|
256
269
|
} catch (e) {
|
package/src/ui/FEAAS.tsx
ADDED
package/src/ui/FEAASContext.tsx
CHANGED
|
@@ -2,11 +2,14 @@ import type * as FEAASContextImports from '@sitecore-feaas/frontend/context'
|
|
|
2
2
|
import type { AuthModel } from '@sitecore-feaas/sdk'
|
|
3
3
|
import { FEAASElement, OptionalExcept } from '../components/FEAASElement.js'
|
|
4
4
|
import { ControlledPromise } from '../utils/promise.js'
|
|
5
|
+
import type * as FEAAS from './index.js'
|
|
5
6
|
|
|
6
7
|
export function FEAASContextProps(element: HTMLElement) {
|
|
7
8
|
return {
|
|
8
9
|
library: element?.getAttribute('library') as string,
|
|
9
10
|
src: element?.getAttribute('src') ?? '/context.js',
|
|
11
|
+
// override environment
|
|
12
|
+
environment: element?.getAttribute('environment') ?? undefined,
|
|
10
13
|
frontend: element?.getAttribute('frontend') ?? 'https://components.sitecorecloud.io',
|
|
11
14
|
backend: element?.getAttribute('backend') ?? 'https://components.sitecorecloud.io/api',
|
|
12
15
|
cdn: element?.getAttribute('cdn') ?? 'https://feaas.blob.core.windows.net',
|
|
@@ -60,6 +63,7 @@ export class FEAASContext extends FEAASElement<FEAASContextProps, typeof FEAASCo
|
|
|
60
63
|
|
|
61
64
|
/** Pull in children FEAAS react elements into the shadow DOM so they receieve the styles */
|
|
62
65
|
connectedCallback() {
|
|
66
|
+
this.setAttribute('feaas-role', 'ui')
|
|
63
67
|
//if (this.props.isolate)
|
|
64
68
|
Array.from(this.children).forEach((child) => {
|
|
65
69
|
this.getRoot().appendChild(child)
|
|
@@ -75,6 +79,7 @@ export class FEAASContext extends FEAASElement<FEAASContextProps, typeof FEAASCo
|
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
sdk: FEAASContextImports.SDK
|
|
82
|
+
clientside: typeof FEAAS
|
|
78
83
|
onStatusChange: FEAASContextImports.SDKStatusCallback = (status, sdk) => {
|
|
79
84
|
if (status == 'ready') {
|
|
80
85
|
this.whenSDKReady.resolve(sdk)
|
|
@@ -99,6 +104,7 @@ export class FEAASContext extends FEAASElement<FEAASContextProps, typeof FEAASCo
|
|
|
99
104
|
}
|
|
100
105
|
return (
|
|
101
106
|
<AppContext
|
|
107
|
+
environment={this.props.environment as any}
|
|
102
108
|
backend={this.props.backend}
|
|
103
109
|
libraryId={this.props.library}
|
|
104
110
|
container={this.getRoot()}
|
|
@@ -109,7 +115,7 @@ export class FEAASContext extends FEAASElement<FEAASContextProps, typeof FEAASCo
|
|
|
109
115
|
portalProps
|
|
110
116
|
}}
|
|
111
117
|
chakraProps={{
|
|
112
|
-
resetScope: '
|
|
118
|
+
resetScope: ':where([feaas-role="ui"])',
|
|
113
119
|
cssVarsRoot: ':host',
|
|
114
120
|
resetCSS: true,
|
|
115
121
|
toastOptions: {
|
|
@@ -129,6 +135,9 @@ declare global {
|
|
|
129
135
|
'feaas-context': OptionalExcept<FEAASContextProps, 'library'>
|
|
130
136
|
}
|
|
131
137
|
}
|
|
138
|
+
interface DocumentEventMap {
|
|
139
|
+
feaasReady: CustomEvent<FEAASContextImports.SDK>
|
|
140
|
+
}
|
|
132
141
|
}
|
|
133
142
|
|
|
134
143
|
export { FEAASContext as Context }
|
package/src/ui/FEAASExternal.tsx
CHANGED
|
@@ -4,7 +4,7 @@ import { FEAASElement } from '../components/FEAASElement.js'
|
|
|
4
4
|
import { ReactJSX, ReactModules } from '../components/FEAASReactElement.js'
|
|
5
5
|
import { kebabCase, reverseKebabCase } from '../utils/string.js'
|
|
6
6
|
import { Component, FEAASComponentProps } from './react.js'
|
|
7
|
-
import type { ExternalComponent, ExternalComponentOptions } from '@sitecore-feaas/sdk'
|
|
7
|
+
import type { ExternalComponent, ExternalComponentOptions, JSONSchema, UISchema } from '@sitecore-feaas/sdk'
|
|
8
8
|
|
|
9
9
|
/** Checks if title exists in a property and if not, it generates it from (kebab-cased) property key */
|
|
10
10
|
const addTitles = (properties: { [key: string]: any }) =>
|
|
@@ -21,13 +21,14 @@ const addTitles = (properties: { [key: string]: any }) =>
|
|
|
21
21
|
return acc
|
|
22
22
|
}, {})
|
|
23
23
|
|
|
24
|
+
/** Converts the keys of an object to kebab case. */
|
|
24
25
|
const objectKeysToKebabCase = (object: { [key: string]: any } = {}) =>
|
|
25
26
|
Object.keys(object).reduce((acc: { [key: string]: any }, key) => {
|
|
26
27
|
acc[kebabCase(key)] = object[key]
|
|
27
28
|
return acc
|
|
28
29
|
}, {})
|
|
29
30
|
|
|
30
|
-
const transformSchema = (schema:
|
|
31
|
+
const transformSchema = (schema: JSONSchema) => {
|
|
31
32
|
if (!schema) return
|
|
32
33
|
if (!schema.properties) return schema
|
|
33
34
|
let properties, required
|
|
@@ -44,9 +45,9 @@ const transformSchema = (schema: ExternalComponent['schema']) => {
|
|
|
44
45
|
return { ...schema, properties, required, title }
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
const transformUiSchema = (uiSchema:
|
|
48
|
+
const transformUiSchema = (uiSchema: UISchema, properties: { [key: string]: any }) => {
|
|
48
49
|
if (!uiSchema || !properties) return
|
|
49
|
-
let transformed:
|
|
50
|
+
let transformed: UISchema
|
|
50
51
|
|
|
51
52
|
/* kebab-case keys */
|
|
52
53
|
transformed = objectKeysToKebabCase(uiSchema)
|
|
@@ -60,12 +61,8 @@ const transformUiSchema = (uiSchema: ExternalComponent['uiSchema'], properties:
|
|
|
60
61
|
|
|
61
62
|
return transformed
|
|
62
63
|
}
|
|
63
|
-
|
|
64
|
-
export const parseProps = (
|
|
65
|
-
propName: string,
|
|
66
|
-
propValue: string,
|
|
67
|
-
properties: ExternalComponent['schema']['properties']
|
|
68
|
-
) => {
|
|
64
|
+
/** Parses a property value based on its property name and type defined in the JSON properties object. */
|
|
65
|
+
export const parseProps = (propName: string, propValue: string, properties: JSONSchema['properties']) => {
|
|
69
66
|
//@ts-ignore
|
|
70
67
|
switch (properties?.[propName]?.type) {
|
|
71
68
|
case 'string':
|
|
@@ -88,7 +85,7 @@ export const parseProps = (
|
|
|
88
85
|
export function FEAASExternalProps(element: FEAASElement) {
|
|
89
86
|
const attributes = element.getAttributeNames()
|
|
90
87
|
const id = element.getAttribute('data-external-id')
|
|
91
|
-
const schema:
|
|
88
|
+
const schema: JSONSchema = FEAASExternal.registered[id]?.schema
|
|
92
89
|
const props = attributes.reduce((prev, curr) => {
|
|
93
90
|
if (!curr.startsWith('data-attribute') && !['class', 'id', 'contenteditable'].includes(curr)) {
|
|
94
91
|
const value = element.getAttribute(curr)
|
|
@@ -123,11 +120,70 @@ export default class FEAASExternal extends FEAASElement<FEAASExternalProps, Reac
|
|
|
123
120
|
return FEAASExternalProps(this)
|
|
124
121
|
}
|
|
125
122
|
|
|
126
|
-
|
|
127
|
-
|
|
123
|
+
/**
|
|
124
|
+
* Register React component to be renderable as Sitecore component (in Components and Pages). Properties are defined
|
|
125
|
+
* as JSON schema, from which a configuration form will be produced using RJSF library. The library allows passing a
|
|
126
|
+
* UI schema that allows tight customization of form widgets, styles and its behavior.
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* FEAAS.External.registerComponent(MyComponent, {
|
|
130
|
+
* // Name of a component as it will be presented to the user
|
|
131
|
+
* name: 'My Component',
|
|
132
|
+
* // Title of a component as it will be presented in the inputs form
|
|
133
|
+
* title: 'My Component',
|
|
134
|
+
* // Optional: Thumbnail image for component to be displayed in UI
|
|
135
|
+
* thumbnail: 'http://examaple.com/component.jpg',
|
|
136
|
+
* // Optional: Group components by name
|
|
137
|
+
* group: 'Assorted components',
|
|
138
|
+
* // Definitions of three configurable properties that MyComponent accepts
|
|
139
|
+
* properties: {
|
|
140
|
+
* // Color is a string, but only Red/Green/Blue are allowed
|
|
141
|
+
* color: { type: 'string', enum: ['red', 'green', 'blue'] },
|
|
142
|
+
* // Text is a property labelled as "Text summary"
|
|
143
|
+
* text: { type: 'string', title: 'Text summary' },
|
|
144
|
+
* // Count is a number with 1 as default value
|
|
145
|
+
* count: { type: 'number', default: 1 }
|
|
146
|
+
* },
|
|
147
|
+
* // Optional UI schema customization
|
|
148
|
+
* ui: {
|
|
149
|
+
* // Present color choices as radio buttons
|
|
150
|
+
* color: { 'ui:widget': 'radio' },
|
|
151
|
+
* // Text is a textarea with 5 rows
|
|
152
|
+
* text: { 'ui:widget': 'textarea', 'ui:options': { rows: 5 } },
|
|
153
|
+
* // Count is a number field with spinner button
|
|
154
|
+
* count: { 'ui:widget': 'updown' }
|
|
155
|
+
* },
|
|
156
|
+
* // Color and text area required properties
|
|
157
|
+
* required: ['color', 'text']
|
|
158
|
+
* })
|
|
159
|
+
*
|
|
160
|
+
* @param {React.ComponentType} component React component that will be rendered.
|
|
161
|
+
* @param {string} options.name Component name as presented to the user.
|
|
162
|
+
* @param {string} options.title Component title to be used in the inputs form. Defaults to component name.
|
|
163
|
+
* @param {JSONSchema['properties']} options.properties Object of properties.
|
|
164
|
+
* @param {JSONSchema['type']} options.properties[].type JSON Schema type of a property.
|
|
165
|
+
* @param {string} [options.properties[].title] Optional title displayed to the user.
|
|
166
|
+
* @param {string} [options.group] Optional Image to be displayed in the UI.
|
|
167
|
+
* @param {string} [options.thumbnail] Optional title displayed to the user.
|
|
168
|
+
* @param {UISchema} [options.ui] Optional component groupping.
|
|
169
|
+
* @see https://rjsf-team.github.io/react-jsonschema-form/docs/api-reference/uiSchema
|
|
170
|
+
* @see https://json-schema.org
|
|
171
|
+
*/
|
|
172
|
+
static registerComponent(component: React.ComponentType, options: ExternalComponentOptions) {
|
|
173
|
+
if (!options.name)
|
|
174
|
+
throw new Error('Could not register external component. Please make sure you provide a name in the options')
|
|
175
|
+
const id = options.name
|
|
128
176
|
const { thumbnail, group, ui: uiSchema, ...schemaOptions } = options
|
|
129
|
-
const schema = { ...schemaOptions, type: 'object' } as
|
|
130
|
-
FEAASExternal.registered[id] = {
|
|
177
|
+
const schema = { ...schemaOptions, type: 'object' } as JSONSchema
|
|
178
|
+
FEAASExternal.registered[id] = {
|
|
179
|
+
component,
|
|
180
|
+
name: id,
|
|
181
|
+
schema,
|
|
182
|
+
uiSchema,
|
|
183
|
+
thumbnail,
|
|
184
|
+
group,
|
|
185
|
+
title: schema?.title || id
|
|
186
|
+
}
|
|
131
187
|
FEAASExternal.setRegistrationCallback()
|
|
132
188
|
}
|
|
133
189
|
|
|
@@ -161,7 +217,7 @@ export default class FEAASExternal extends FEAASElement<FEAASExternalProps, Reac
|
|
|
161
217
|
|
|
162
218
|
/*static loadComponents() {
|
|
163
219
|
const components = JSON.parse(sessionStorage.getItem('feaas-external-components'))
|
|
164
|
-
components.forEach((c: { name: string; schema: any; component: React.
|
|
220
|
+
components.forEach((c: { name: string; schema: any; component: React.ComponentType }) => {
|
|
165
221
|
const name = c.name
|
|
166
222
|
const schema = c.schema
|
|
167
223
|
const component = eval('(' + c.component + ')')
|
|
@@ -192,4 +248,5 @@ declare global {
|
|
|
192
248
|
FEAASExternal.register('feaas-external')
|
|
193
249
|
FEAASExternal.setRegistrationCallback()
|
|
194
250
|
|
|
251
|
+
export const registerComponent = FEAASExternal.registerComponent
|
|
195
252
|
export { FEAASExternal as External }
|
package/src/ui/FEAASPicker.tsx
CHANGED
|
@@ -5,7 +5,7 @@ export function FEAASPickerProps(element: FEAASElement<any, any>) {
|
|
|
5
5
|
return {
|
|
6
6
|
part: element?.getAttribute('part') || 'picker',
|
|
7
7
|
slot: element?.getAttribute('part') || 'picker',
|
|
8
|
-
mode: element?.getAttribute('mode') as 'library' | 'element' | 'themes',
|
|
8
|
+
mode: element?.getAttribute('mode') as 'library' | 'element' | 'themes' | 'datasources',
|
|
9
9
|
placeholder: element?.getAttribute('placeholder') || 'No theme',
|
|
10
10
|
frontend: element?.getContextAttribute('frontend') ?? 'https://components.sitecorecloud.io',
|
|
11
11
|
src: element?.getAttribute('src') ?? '/picker.js',
|
|
@@ -32,7 +32,10 @@ export class FEAASPicker extends FEAASElement<FEAASPickerProps, typeof FEAASPick
|
|
|
32
32
|
load() {
|
|
33
33
|
return this.import(this.formatURL(this.props.src, this.props.frontend))
|
|
34
34
|
}
|
|
35
|
-
|
|
35
|
+
connectedCallback() {
|
|
36
|
+
this.setAttribute('feaas-role', 'ui')
|
|
37
|
+
super.connectedCallback()
|
|
38
|
+
}
|
|
36
39
|
render(
|
|
37
40
|
{ placeholder, context, mode, children, variant }: React.PropsWithChildren<FEAASPickerProps>,
|
|
38
41
|
{ Picker, React, useLibrary }: typeof FEAASPickerImports
|
package/src/ui/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export * from '
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
export * from './FEAAS.js'
|
|
2
|
+
import * as FEAAS from './FEAAS.js'
|
|
3
|
+
|
|
4
|
+
FEAAS.Context.prototype.clientside = FEAAS
|
package/src/ui/react.tsx
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { DatasourceModel, SDK } from '@sitecore-feaas/sdk'
|
|
2
|
+
|
|
1
3
|
export * from './index.js'
|
|
2
4
|
import { FEAASComponentProps, parseComponentSource } from './index.js'
|
|
3
5
|
import React from 'react'
|
|
4
6
|
import { renderHTMLContent } from '../dom/html.js'
|
|
7
|
+
|
|
5
8
|
export { renderHTMLContent }
|
|
6
9
|
export { External } from './FEAASExternal.js'
|
|
7
10
|
|
|
@@ -9,12 +12,14 @@ export function FEAASEmbed({ data, ...props }: FEAASComponentProps<any>) {
|
|
|
9
12
|
if ('template' in props && props.template) {
|
|
10
13
|
var html = renderHTMLContent(props.template, data || {})
|
|
11
14
|
}
|
|
15
|
+
|
|
12
16
|
if ('src' in props) {
|
|
13
17
|
props = {
|
|
14
18
|
...parseComponentSource(props.src),
|
|
15
19
|
...props
|
|
16
20
|
}
|
|
17
21
|
}
|
|
22
|
+
|
|
18
23
|
return (
|
|
19
24
|
<feaas-component
|
|
20
25
|
{...props}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
type Data = object | any[]
|
|
2
|
+
|
|
3
|
+
type DataSettings = { jsonpath: string; url: string }
|
|
4
|
+
|
|
5
|
+
export const isDataSettings = (data: any): data is DataSettings =>
|
|
6
|
+
typeof data === 'object' && 'jsonpath' in data && 'url' in data
|
|
7
|
+
|
|
8
|
+
const performFetch = async (
|
|
9
|
+
settings: Parameters<typeof fetch>[1] & {
|
|
10
|
+
url: Parameters<typeof fetch>[0]
|
|
11
|
+
}
|
|
12
|
+
): Promise<Data> => {
|
|
13
|
+
try {
|
|
14
|
+
const response = await fetch(settings.url, settings)
|
|
15
|
+
|
|
16
|
+
if (!response) return {}
|
|
17
|
+
|
|
18
|
+
return await response.json()
|
|
19
|
+
} catch (e) {
|
|
20
|
+
return {}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function fetchData(data: DataSettings | Record<string, DataSettings | Data> | Data): Promise<Data> {
|
|
25
|
+
if (!data) return data
|
|
26
|
+
|
|
27
|
+
console.log(data)
|
|
28
|
+
|
|
29
|
+
if (isDataSettings(data)) return performFetch(data)
|
|
30
|
+
|
|
31
|
+
const keys = Object.keys(data)
|
|
32
|
+
|
|
33
|
+
const allRequests = await Promise.all(
|
|
34
|
+
keys.map((key: keyof typeof data) => (isDataSettings(data[key]) ? performFetch(data[key]) : null)).filter(Boolean)
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
return keys.reduce(
|
|
38
|
+
(obj, key: keyof typeof data) => ({
|
|
39
|
+
...obj,
|
|
40
|
+
[key]: isDataSettings(data[key]) ? allRequests.shift() : data[key]
|
|
41
|
+
}),
|
|
42
|
+
{}
|
|
43
|
+
)
|
|
44
|
+
}
|
|
@@ -6,6 +6,7 @@ import { clearFetchCache } from '../src/fetch.js'
|
|
|
6
6
|
const earliest = new Date(2000, 1, 1, 0, 0, 0)
|
|
7
7
|
const earlier = new Date(2000, 1, 2, 0, 0, 0)
|
|
8
8
|
const now = new Date(2000, 1, 2, 12, 0, 0)
|
|
9
|
+
|
|
9
10
|
describe('Components', () => {
|
|
10
11
|
beforeAll(() => {
|
|
11
12
|
// vi.useFakeTimers()
|