@sitecore-feaas/clientside 0.2.1 → 0.3.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 +7 -7
- package/build/components/FEAASComponent.js +72 -10
- package/build/components/FEAASComponent.js.map +1 -1
- package/build/components/FEAASElement.js +19 -5
- package/build/components/FEAASElement.js.map +1 -1
- package/build/dom/rendering.js.map +1 -1
- package/build/dom/thumbnail.js +29 -24
- package/build/dom/thumbnail.js.map +1 -1
- package/build/headless.js +5 -0
- package/build/headless.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 +68 -4
- package/build/ui/FEAASExternal.js.map +1 -1
- package/build/ui/FEAASPicker.js +15 -5
- 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 +23 -8
- package/build/ui/react.js.map +1 -1
- package/build/utils/datapath.js +15 -0
- package/build/utils/datapath.js.map +1 -1
- package/build/utils/fetch.js +40 -0
- package/build/utils/fetch.js.map +1 -0
- package/build/utils/settings.js +60 -0
- package/build/utils/settings.js.map +1 -0
- package/dist/browser/headless.cjs +8 -6
- package/dist/browser/headless.cjs.map +4 -4
- package/dist/browser/headless.esm.js +8 -6
- package/dist/browser/headless.esm.js.map +1 -1
- package/dist/browser/index.cjs +8 -6
- package/dist/browser/index.cjs.map +4 -4
- package/dist/browser/index.esm.js +8 -6
- package/dist/browser/index.esm.js.map +1 -1
- package/dist/browser/react.cjs +8 -6
- package/dist/browser/react.cjs.map +4 -4
- package/dist/browser/react.esm.js +8 -6
- package/dist/browser/react.esm.js.map +1 -1
- package/dist/node/headless.cjs +6 -4
- package/dist/node/headless.cjs.map +4 -4
- package/dist/node/headless.esm.js +6 -4
- package/dist/node/headless.esm.js.map +4 -4
- package/dist/node/index.cjs +7 -5
- package/dist/node/index.cjs.map +4 -4
- package/dist/node/index.esm.js +7 -5
- package/dist/node/index.esm.js.map +4 -4
- package/dist/node/react.cjs +6 -4
- package/dist/node/react.cjs.map +4 -4
- package/dist/node/react.esm.js +6 -4
- package/dist/node/react.esm.js.map +4 -4
- package/headless.cjs +6 -4
- package/headless.cjs.map +4 -4
- package/headless.js +6 -4
- package/headless.js.map +4 -4
- package/package.json +2 -2
- package/react.cjs +8 -6
- package/react.cjs.map +4 -4
- package/react.js +8 -6
- package/react.js.map +4 -4
- package/scripts/build.js +1 -1
- package/src/components/FEAASComponent.tsx +101 -10
- package/src/components/FEAASElement.tsx +30 -5
- package/src/dom/rendering.ts +1 -0
- package/src/dom/thumbnail.ts +29 -24
- package/src/headless.ts +5 -0
- package/src/ui/FEAAS.tsx +4 -0
- package/src/ui/FEAASContext.tsx +10 -1
- package/src/ui/FEAASExternal.tsx +75 -16
- package/src/ui/FEAASPicker.tsx +19 -7
- package/src/ui/index.ts +4 -4
- package/src/ui/react.tsx +48 -14
- package/src/utils/datapath.ts +17 -0
- package/src/utils/fetch.ts +60 -0
- package/src/utils/settings.ts +69 -0
- package/test/FEAASComponent.react.test.tsx +1 -1
- package/test/FEAASComponent.test.tsx +1 -0
- package/test/FEAASComponentData.test.tsx +215 -0
- package/test/FEAASContext.test.tsx +3 -3
- package/test/FEAASExternal.test.tsx +1 -8
- package/test/ServerComponent.test.tsx +33 -0
- package/test/fetch-data.ts +55 -0
- package/test/settings.test.ts +78 -0
- package/types/components/FEAASComponent.d.ts +33 -1
- package/types/components/FEAASComponent.d.ts.map +1 -1
- package/types/components/FEAASElement.d.ts +10 -1
- package/types/components/FEAASElement.d.ts.map +1 -1
- package/types/dom/rendering.d.ts.map +1 -1
- package/types/dom/thumbnail.d.ts +16 -16
- package/types/dom/thumbnail.d.ts.map +1 -1
- package/types/headless.d.ts +5 -0
- package/types/headless.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 +56 -3
- package/types/ui/FEAASExternal.d.ts.map +1 -1
- package/types/ui/FEAASPicker.d.ts +10 -6
- 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 +7 -2
- package/types/ui/react.d.ts.map +1 -1
- package/types/ui/tsbuildinfo +1 -1
- package/types/utils/datapath.d.ts +2 -0
- package/types/utils/datapath.d.ts.map +1 -1
- package/types/utils/fetch.d.ts +9 -0
- package/types/utils/fetch.d.ts.map +1 -0
- package/types/utils/settings.d.ts +13 -0
- package/types/utils/settings.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) {
|
|
@@ -11,12 +13,17 @@ export function FEAASComponentsProps(element: FEAASElement) {
|
|
|
11
13
|
if (!parsed) throw new Error(`Could not parse FEAAS Component source: ${src}`)
|
|
12
14
|
var { cdn, library, component, version, revision } = parsed
|
|
13
15
|
}
|
|
16
|
+
|
|
14
17
|
const dataValue = element.getAttribute('data')
|
|
18
|
+
|
|
15
19
|
if (dataValue != null && dataValue != '') {
|
|
16
20
|
try {
|
|
17
21
|
data = typeof dataValue == 'string' ? JSON.parse(dataValue) : {}
|
|
18
|
-
} catch (e) {
|
|
22
|
+
} catch (e) {
|
|
23
|
+
console.error(e)
|
|
24
|
+
}
|
|
19
25
|
}
|
|
26
|
+
|
|
20
27
|
return {
|
|
21
28
|
data: data || {},
|
|
22
29
|
cdn: element.getContextAttribute('cdn') ?? cdn ?? DEFAULT_CDN_URL,
|
|
@@ -33,8 +40,40 @@ export function FEAASComponentsProps(element: FEAASElement) {
|
|
|
33
40
|
withStylesheet: element.getAttribute('with-stylesheet') != 'false'
|
|
34
41
|
}
|
|
35
42
|
}
|
|
43
|
+
|
|
36
44
|
export type FEAASComponentsProps = ReturnType<typeof FEAASComponentsProps>
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* - Initialization lifecycle:
|
|
48
|
+
* - - `set(props)`
|
|
49
|
+
* - - `scheduleRender()` - any attribute change causes render to schedule to next frame
|
|
50
|
+
* - - `whenRendered.restart()` - rendering promise is reset and will resolve after render
|
|
51
|
+
* - - `advance()` - called on next frame
|
|
52
|
+
* - - `if (isReadyToLoad()) => boolean` - check if component has all necessary properties to load
|
|
53
|
+
* - - `load() => payload` => component is loaded
|
|
54
|
+
* - - `onLoad?(payload)` => invoke loaded callback for cached version of a component
|
|
55
|
+
* - - `advance()` => going further in state machine
|
|
56
|
+
* - - `flush()` => flushes all react changes if needed
|
|
57
|
+
* - - `update()` => component-specific method (or react) to render changes to dom
|
|
58
|
+
* - - `render(payload, props)` => render means react
|
|
59
|
+
* - - `whenRendered.resolve()` - rendering promise is resolved after rendering
|
|
60
|
+
* - - IF CACHE WAS STALE - FETCH AND RENDER AGAIN
|
|
61
|
+
* - - `fetchByPassingCache()` - load up-to-date version of a component
|
|
62
|
+
* - - `onLoad(newPayload) `
|
|
63
|
+
* - - `advance()` => going further in state machine
|
|
64
|
+
* - - `flush()` => flushes all react changes if needed
|
|
65
|
+
* - - `update()` => component-specific method (or react) to render changes to dom
|
|
66
|
+
* - - `render(payload, props)` => render means react
|
|
67
|
+
* - - `whenRendered.resolve()` - rendering promise is resolved after rendering
|
|
68
|
+
* - - `whenLoaded.resolved()` - resolved after loading of up to date component
|
|
69
|
+
*/
|
|
37
70
|
export class FEAASComponent extends FEAASElement<FEAASComponentsProps, string> {
|
|
71
|
+
readyData: any = {}
|
|
72
|
+
whenDataReady = ControlledPromise<any>(
|
|
73
|
+
(data) => (this.readyData = data),
|
|
74
|
+
(e) => this.onError(e)
|
|
75
|
+
)
|
|
76
|
+
|
|
38
77
|
static observedAttributes = [
|
|
39
78
|
'library',
|
|
40
79
|
'component',
|
|
@@ -59,6 +98,7 @@ export class FEAASComponent extends FEAASElement<FEAASComponentsProps, string> {
|
|
|
59
98
|
} as Partial<FEAASComponentsProps>
|
|
60
99
|
|
|
61
100
|
needsRefresh: boolean
|
|
101
|
+
|
|
62
102
|
setData(data: any) {
|
|
63
103
|
this.set({ data })
|
|
64
104
|
}
|
|
@@ -69,12 +109,35 @@ export class FEAASComponent extends FEAASElement<FEAASComponentsProps, string> {
|
|
|
69
109
|
|
|
70
110
|
attributeChangedCallback(name: string, oldValue: string, newValue: string): void {
|
|
71
111
|
if (oldValue === newValue) return
|
|
112
|
+
|
|
72
113
|
// force component to be refreshed
|
|
73
114
|
if (name == 'library' || name == 'component' || name == 'revision' || name == 'version') {
|
|
74
115
|
this.payload = undefined
|
|
75
116
|
}
|
|
117
|
+
|
|
76
118
|
this.needsRefresh = true
|
|
119
|
+
|
|
77
120
|
super.attributeChangedCallback(name, oldValue, newValue)
|
|
121
|
+
|
|
122
|
+
if (name === 'data') {
|
|
123
|
+
this.fetchData()
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Reset readyData and perform a new fetch.
|
|
128
|
+
async fetchData() {
|
|
129
|
+
this.readyData = undefined
|
|
130
|
+
|
|
131
|
+
this.whenDataReady = this.whenDataReady.restart()
|
|
132
|
+
|
|
133
|
+
const data = await fetchData(this.props.data)
|
|
134
|
+
|
|
135
|
+
const alreadyRendered = this.alreadyRendered
|
|
136
|
+
|
|
137
|
+
await this.whenDataReady.resolve(data)
|
|
138
|
+
if (alreadyRendered) {
|
|
139
|
+
this.advance()
|
|
140
|
+
}
|
|
78
141
|
}
|
|
79
142
|
|
|
80
143
|
getEditor(): any {
|
|
@@ -83,6 +146,11 @@ export class FEAASComponent extends FEAASElement<FEAASComponentsProps, string> {
|
|
|
83
146
|
|
|
84
147
|
constructor() {
|
|
85
148
|
super()
|
|
149
|
+
|
|
150
|
+
if (!this.getAttribute('data')) {
|
|
151
|
+
this.whenDataReady.resolve({})
|
|
152
|
+
}
|
|
153
|
+
|
|
86
154
|
this.addEventListener('click', () => {
|
|
87
155
|
if (this.props.editable) {
|
|
88
156
|
this.edit()
|
|
@@ -125,36 +193,57 @@ export class FEAASComponent extends FEAASElement<FEAASComponentsProps, string> {
|
|
|
125
193
|
|
|
126
194
|
async load() {
|
|
127
195
|
// defer loading to original element
|
|
128
|
-
if (this.getOriginal())
|
|
129
|
-
|
|
196
|
+
if (this.getOriginal()) return
|
|
197
|
+
|
|
198
|
+
const advanceIfEverythingIsReady = () => {
|
|
199
|
+
// html is not loaded
|
|
200
|
+
if (!this.payload) return
|
|
201
|
+
|
|
202
|
+
// shared stylesheet is still being loaded
|
|
203
|
+
if (stylesheetPromise) return
|
|
204
|
+
|
|
205
|
+
// data is still loading
|
|
206
|
+
if (!this.readyData) return
|
|
207
|
+
|
|
208
|
+
this.onLoad(this.payload)
|
|
130
209
|
}
|
|
210
|
+
|
|
131
211
|
if (this.props.withStylesheet) {
|
|
132
212
|
var stylesheetPromise = loadStylesheetAllowStale({ ...this.props, revision: 'published' }).then(() => {
|
|
133
213
|
stylesheetPromise = null
|
|
134
|
-
|
|
135
|
-
this.onLoad(this.payload)
|
|
136
|
-
}
|
|
214
|
+
advanceIfEverythingIsReady()
|
|
137
215
|
})
|
|
138
216
|
}
|
|
217
|
+
|
|
139
218
|
return Promise.all([
|
|
140
219
|
stylesheetPromise,
|
|
220
|
+
this.whenDataReady.then(() => advanceIfEverythingIsReady()),
|
|
141
221
|
fetchAndRevalidateComponent(this.props, (html) => {
|
|
142
222
|
this.payload = html
|
|
143
|
-
|
|
223
|
+
advanceIfEverythingIsReady()
|
|
144
224
|
})
|
|
145
225
|
]).then(() => this.payload)
|
|
146
226
|
}
|
|
147
227
|
|
|
228
|
+
alreadyRendered: boolean
|
|
229
|
+
|
|
230
|
+
flush() {
|
|
231
|
+
if (!this.readyData) return
|
|
232
|
+
|
|
233
|
+
super.flush()
|
|
234
|
+
}
|
|
235
|
+
|
|
148
236
|
update() {
|
|
237
|
+
this.alreadyRendered = true
|
|
149
238
|
if (Array.from(this.children).filter((c) => c.tagName != 'BR').length > 0 && !this.needsRefresh) {
|
|
150
|
-
renderDOMElement(this, this.
|
|
239
|
+
renderDOMElement(this, this.readyData)
|
|
151
240
|
} else {
|
|
152
241
|
this.needsRefresh = true
|
|
153
242
|
// defer rendering to original element
|
|
154
243
|
if (this.getOriginal()) {
|
|
155
244
|
return
|
|
156
245
|
}
|
|
157
|
-
renderDOMContent(this, this.payload, this.
|
|
246
|
+
renderDOMContent(this, this.payload, this.readyData)
|
|
158
247
|
}
|
|
159
248
|
}
|
|
160
249
|
}
|
|
@@ -173,7 +262,9 @@ declare global {
|
|
|
173
262
|
'with-stylesheet'?: 'false' | 'true' | ''
|
|
174
263
|
} & (
|
|
175
264
|
| {
|
|
176
|
-
/**
|
|
265
|
+
/**
|
|
266
|
+
* @deprecated Use cdn instead.
|
|
267
|
+
*/
|
|
177
268
|
hostname?: string
|
|
178
269
|
cdn?: string
|
|
179
270
|
library: string
|
|
@@ -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
|
*
|
|
@@ -108,7 +118,7 @@ export class FEAASElement<Props = any, Payload = any> extends WebComponent {
|
|
|
108
118
|
if (this.connected) return
|
|
109
119
|
this.connected = true
|
|
110
120
|
this.log('FEAAS: Mount', this.tagName, this)
|
|
111
|
-
this.
|
|
121
|
+
this.dispatchEventIndirectly(new Event('feaasMount', { bubbles: true, composed: true }))
|
|
112
122
|
this.getElements().map((element: FEAASElement) => element.mount?.())
|
|
113
123
|
this.set()
|
|
114
124
|
} catch (e) {
|
|
@@ -116,6 +126,17 @@ export class FEAASElement<Props = any, Payload = any> extends WebComponent {
|
|
|
116
126
|
}
|
|
117
127
|
}
|
|
118
128
|
|
|
129
|
+
dispatchEventIndirectly(event: Event) {
|
|
130
|
+
this.dispatchEvent(event)
|
|
131
|
+
const parent = this.getParent() as FEAASElement
|
|
132
|
+
|
|
133
|
+
if (parent && !parent.contains(this)) {
|
|
134
|
+
Object.defineProperty(event, 'target', { value: this, writable: false })
|
|
135
|
+
Object.defineProperty(event, 'composedPath', { value: () => [this], writable: false })
|
|
136
|
+
parent.handleEvent(event)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
119
140
|
log(...args: any) {
|
|
120
141
|
if (typeof process != 'undefined' && process.env.TESTING) return
|
|
121
142
|
console.log(...args)
|
|
@@ -125,7 +146,7 @@ export class FEAASElement<Props = any, Payload = any> extends WebComponent {
|
|
|
125
146
|
unmount() {
|
|
126
147
|
if (!this.connected) return
|
|
127
148
|
this.log('FEAAS: Unmount', this)
|
|
128
|
-
this.
|
|
149
|
+
this.dispatchEventIndirectly(new Event('feaasUnmount', { bubbles: true, composed: true }))
|
|
129
150
|
this.connected = false
|
|
130
151
|
}
|
|
131
152
|
|
|
@@ -137,6 +158,8 @@ export class FEAASElement<Props = any, Payload = any> extends WebComponent {
|
|
|
137
158
|
) {
|
|
138
159
|
if (!onlyFeaas || p.tagName.startsWith('FEAAS-')) return p
|
|
139
160
|
}
|
|
161
|
+
// feaas context anywhere in the dom acts as a root element
|
|
162
|
+
if (this.tagName != 'FEAAS-CONTEXT') return document.querySelector('feaas-context')
|
|
140
163
|
}
|
|
141
164
|
|
|
142
165
|
getElements(root: HTMLElement | ShadowRoot = this.getRoot(), onlyFeaas = true): HTMLElement[] {
|
|
@@ -170,7 +193,7 @@ export class FEAASElement<Props = any, Payload = any> extends WebComponent {
|
|
|
170
193
|
const index = this.descendants.indexOf(target)
|
|
171
194
|
if (index == -1) return
|
|
172
195
|
this.log('FEAAS: Unnest', this, target)
|
|
173
|
-
this.descendants.splice(index,
|
|
196
|
+
this.descendants.splice(index, 1)
|
|
174
197
|
this.refs[target.getComponentName()] = null
|
|
175
198
|
if (target.refs.parent == this) target.refs.parent = null
|
|
176
199
|
if (target.refs.top == this) {
|
|
@@ -212,7 +235,7 @@ export class FEAASElement<Props = any, Payload = any> extends WebComponent {
|
|
|
212
235
|
/** Generic error handler */
|
|
213
236
|
onError(error: Error, where: string = 'uncaught', handled = false) {
|
|
214
237
|
const event = new CustomEvent('feaasError', { bubbles: true, composed: true, detail: error })
|
|
215
|
-
this.
|
|
238
|
+
this.dispatchEventIndirectly(event)
|
|
216
239
|
this.whenLoaded.reject(error)
|
|
217
240
|
if (!event.defaultPrevented && !handled) {
|
|
218
241
|
this.whenRendered.reject(error)
|
|
@@ -236,7 +259,7 @@ export class FEAASElement<Props = any, Payload = any> extends WebComponent {
|
|
|
236
259
|
if (this.payload === undefined) {
|
|
237
260
|
if (!this.isReadyToLoad()) return
|
|
238
261
|
this.payload = null
|
|
239
|
-
this.
|
|
262
|
+
this.dispatchEventIndirectly(new Event('feaasLoad', { bubbles: true, composed: true }))
|
|
240
263
|
const loaded = await this.load().catch((e) => {
|
|
241
264
|
this.payload = undefined
|
|
242
265
|
throw e
|
|
@@ -251,6 +274,7 @@ export class FEAASElement<Props = any, Payload = any> extends WebComponent {
|
|
|
251
274
|
} else if (this.payload == null) {
|
|
252
275
|
return
|
|
253
276
|
}
|
|
277
|
+
|
|
254
278
|
this.flush()
|
|
255
279
|
this.whenRendered.resolve(this)
|
|
256
280
|
} catch (e) {
|
|
@@ -259,6 +283,7 @@ export class FEAASElement<Props = any, Payload = any> extends WebComponent {
|
|
|
259
283
|
}
|
|
260
284
|
|
|
261
285
|
flush() {
|
|
286
|
+
if (!this.connected) return
|
|
262
287
|
const flushSync = (this.payload as ReactPayload)?.ReactDOM?.flushSync
|
|
263
288
|
if (flushSync) {
|
|
264
289
|
flushSync(() => this.update())
|
package/src/dom/rendering.ts
CHANGED
package/src/dom/thumbnail.ts
CHANGED
|
@@ -25,9 +25,9 @@ export const responseToImage = (response: Response): Promise<HTMLImageElement> =
|
|
|
25
25
|
* The function ensures that the version data for the component is available and selects the one that is most suitable
|
|
26
26
|
* for a display width of 800 pixels.
|
|
27
27
|
*
|
|
28
|
-
* @param {ComponentModel} component
|
|
28
|
+
* @param {ComponentModel} component - The component for which the version needs to be obtained.
|
|
29
29
|
* @returns {Promise<VersionModel>} - A promise that resolves to the most suitable version for a display width of 800
|
|
30
|
-
*
|
|
30
|
+
* pixels.
|
|
31
31
|
*/
|
|
32
32
|
export async function getVersionForThumbnail(component: ComponentModel): Promise<VersionModel> {
|
|
33
33
|
if (!component.versions.length) await component.versions.fetch()
|
|
@@ -45,9 +45,9 @@ export async function getVersionForThumbnail(component: ComponentModel): Promise
|
|
|
45
45
|
* 1. The first call can happen with the stale cached or outdated image.
|
|
46
46
|
* 2. The second call occurs with the up-to-date loaded or freshly generated generated image.
|
|
47
47
|
*
|
|
48
|
-
* @param {ComponentModel} component
|
|
49
|
-
* @param {VersionModel}
|
|
50
|
-
* @param {Callback}
|
|
48
|
+
* @param {ComponentModel} component - Relevant component.
|
|
49
|
+
* @param {VersionModel} [version] - Relevant version (optional, falls back to responsive).
|
|
50
|
+
* @param {Callback} [callback] - Callback to handle the loaded or generated image (optional).
|
|
51
51
|
* @returns {Promise<HTMLImageElement | null>} - The final thumbnail image (either loaded or freshly generated).
|
|
52
52
|
*/
|
|
53
53
|
export async function get(component: ComponentModel, version?: VersionModel, callback?: Callback) {
|
|
@@ -83,16 +83,16 @@ export async function get(component: ComponentModel, version?: VersionModel, cal
|
|
|
83
83
|
*
|
|
84
84
|
* 1. The first call occurs if the image was previously cached by the browser, but the cache is stale.
|
|
85
85
|
* 2. The second call happens when the image is updated from a fresh download. By this design, the user gets to see a
|
|
86
|
-
*
|
|
86
|
+
* cached image immediately while the updated image is being downloaded in the background.
|
|
87
87
|
*
|
|
88
88
|
* If the version of a component is known to be newer than remote file, the function will throw `InvalidResponse` error.
|
|
89
89
|
* As a way to handle that error, the `generate` function may be used to produce new thumbnail (@see render).
|
|
90
90
|
*
|
|
91
|
-
* @param {ComponentModel} component
|
|
92
|
-
* @param {VersionModel}
|
|
93
|
-
*
|
|
94
|
-
* @param {Callback}
|
|
95
|
-
*
|
|
91
|
+
* @param {ComponentModel} component - The component to load a thumbnail for.
|
|
92
|
+
* @param {VersionModel} [version] - The version of the component to load a thumbnail for. If not provided, a
|
|
93
|
+
* version suitable for a responsive component is selected.
|
|
94
|
+
* @param {Callback} [callback] - A callback to handle the loaded image. This callback may be invoked twice for
|
|
95
|
+
* the reasons described above.
|
|
96
96
|
* @returns {Promise<HTMLImageElement>} - The thumbnail image for the component/version combination.
|
|
97
97
|
*/
|
|
98
98
|
export async function load(
|
|
@@ -143,8 +143,8 @@ export async function load(
|
|
|
143
143
|
* thumbnail. It then leverages `render` to generate an image with provided parameters and finally, if the component
|
|
144
144
|
* isn't new, uploads the generated as a Blob onto cdn for future retrieval.
|
|
145
145
|
*
|
|
146
|
-
* @param {ComponentModel} component
|
|
147
|
-
* @param {VersionModel}
|
|
146
|
+
* @param {ComponentModel} component - Component instance.
|
|
147
|
+
* @param {VersionModel} [version] - Version instance.
|
|
148
148
|
* @returns {Promise<HTMLImageElement | null>} Thumbnail image or null if generation isn't possible.
|
|
149
149
|
*/
|
|
150
150
|
export async function generate(component: ComponentModel, version?: VersionModel) {
|
|
@@ -174,15 +174,15 @@ export async function generate(component: ComponentModel, version?: VersionModel
|
|
|
174
174
|
* injected into the iframe to capture the rendered component as an image. The image is created respecting the provided
|
|
175
175
|
* design breakpoint, ensuring an accurate visual representation.
|
|
176
176
|
*
|
|
177
|
-
* @param {string[]}
|
|
178
|
-
* @param {Style.Rule<'breakpoint'>} breakpoint
|
|
179
|
-
*
|
|
180
|
-
* @param {string}
|
|
181
|
-
* @param {any}
|
|
182
|
-
* @param {string}
|
|
183
|
-
* @param {EnvironmentVariables}
|
|
177
|
+
* @param {string[]} classList - Array of class names applied to the rendered component.
|
|
178
|
+
* @param {Style.Rule<'breakpoint'>} breakpoint - Breakpoint style rule that dictates the viewport dimensions for
|
|
179
|
+
* rendering.
|
|
180
|
+
* @param {string} template - String template of the component to render.
|
|
181
|
+
* @param {any} data - Data object to be used when rendering the component.
|
|
182
|
+
* @param {string} cssText - CSS rules to be applied during component rendering.
|
|
183
|
+
* @param {EnvironmentVariables} env - Environment variables used to set frontend and backend endpoint URLs.
|
|
184
184
|
* @returns {Promise<BasicImage>} - Promise that resolves to an object containing the src, width, and height of the
|
|
185
|
-
*
|
|
185
|
+
* created image.
|
|
186
186
|
*/
|
|
187
187
|
function render(
|
|
188
188
|
classList: string[],
|
|
@@ -239,8 +239,13 @@ function render(
|
|
|
239
239
|
${cssText}
|
|
240
240
|
body, html { padding: 0; margin: 0; overflow: hidden }
|
|
241
241
|
`
|
|
242
|
+
const stylesheet2 = iframe.contentWindow.document.createElement('style')
|
|
243
|
+
stylesheet2.textContent = `
|
|
244
|
+
body, html { padding: 0; margin: 0; overflow: hidden }
|
|
245
|
+
`
|
|
242
246
|
|
|
243
247
|
iframe.contentWindow.document.head.appendChild(stylesheet)
|
|
248
|
+
iframe.contentWindow.document.head.appendChild(stylesheet2)
|
|
244
249
|
const root = renderComponent({ template, data })
|
|
245
250
|
root.classList.add(...classList, '-feaas--preview', '-emulate--' + breakpoint.details.slug)
|
|
246
251
|
iframe.contentWindow.document.body.appendChild(root)
|
|
@@ -254,9 +259,9 @@ function render(
|
|
|
254
259
|
* several metadata points, including identifiers and timestamps. Upon successful upload, it sends a no-cache fetch
|
|
255
260
|
* request to the constructed URL to bust the cache.
|
|
256
261
|
*
|
|
257
|
-
* @param {ComponentModel} component
|
|
258
|
-
* @param {VersionModel}
|
|
259
|
-
* @param {Blob}
|
|
262
|
+
* @param {ComponentModel} component - Component instance.
|
|
263
|
+
* @param {VersionModel} version - Version instance.
|
|
264
|
+
* @param {Blob} blob - Blob instance to upload.
|
|
260
265
|
* @returns {Promise<string>} URL of the uploaded Blob.
|
|
261
266
|
*/
|
|
262
267
|
export async function upload(component: ComponentModel, version: VersionModel, blob: Blob): Promise<string> {
|
package/src/headless.ts
CHANGED
|
@@ -19,6 +19,11 @@ export * from './components/FEAASComponent.js'
|
|
|
19
19
|
export * from './components/FEAASLoader.js'
|
|
20
20
|
export * from './components/FEAASStylesheet.js'
|
|
21
21
|
export * from './utils/datapath.js'
|
|
22
|
+
export * as DOM from './dom/rendering.js'
|
|
23
|
+
export * as Datascope from './dom/data.js'
|
|
24
|
+
export * as Datapath from './utils/datapath.js'
|
|
25
|
+
export * from './utils/fetch.js'
|
|
26
|
+
export * as DataSettings from './utils/settings.js'
|
|
22
27
|
export * as Thumbnail from './dom/thumbnail.js'
|
|
23
28
|
|
|
24
29
|
declare global {
|
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,72 @@ 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 {@link https://json-schema.org JSON Schema}, from which a configuration form will be produced using
|
|
126
|
+
* {@link https://rjsf-team.github.io/react-jsonschema-form/ RJSF library}. The library allows passing a
|
|
127
|
+
* {@link https://rjsf-team.github.io/react-jsonschema-form/docs/api-reference/uiSchema UI Schema} that allows tight
|
|
128
|
+
* customization of form widgets, styles and behavior.
|
|
129
|
+
*
|
|
130
|
+
* @param {React.ComponentType} component React component that will be rendered.
|
|
131
|
+
* @param {string} options.title Component name as presented to the user.
|
|
132
|
+
* @param {JSONSchema['properties']} options.properties Object of properties.
|
|
133
|
+
* @param {JSONSchema['type']} options.properties[].type JSON Schema type of a property.
|
|
134
|
+
* @param {string} [options.properties[].title] Optional title displayed to the user.
|
|
135
|
+
* @param {string} [options.group] Optional Image to be displayed in the UI.
|
|
136
|
+
* @param {string} [options.thumbnail] Optional title displayed to the user.
|
|
137
|
+
* @param {UISchema} [options.ui] Optional component groupping.
|
|
138
|
+
* @see https://rjsf-team.github.io/react-jsonschema-form/docs/api-reference/uiSchema
|
|
139
|
+
* @see https://json-schema.org
|
|
140
|
+
* @example
|
|
141
|
+
*
|
|
142
|
+
* FEAAS.External.registerComponent(MyComponent, {
|
|
143
|
+
* // Name of a component as it will be presented to the user
|
|
144
|
+
* name: 'My Component',
|
|
145
|
+
* // Title of a component as it will be presented in the inputs form
|
|
146
|
+
* title: 'My Component',
|
|
147
|
+
* // Optional: Thumbnail image for component to be displayed in UI
|
|
148
|
+
* thumbnail: 'http://examaple.com/component.jpg',
|
|
149
|
+
* // Optional: Group components by name
|
|
150
|
+
* group: 'Assorted components',
|
|
151
|
+
* // Definitions of three configurable properties that MyComponent accepts
|
|
152
|
+
* properties: {
|
|
153
|
+
* // Color is a string, but only Red/Green/Blue are allowed
|
|
154
|
+
* color: { type: 'string', enum: ['red', 'green', 'blue'] },
|
|
155
|
+
* // Text is a property labelled as "Text summary"
|
|
156
|
+
* text: { type: 'string', title: 'Text summary' },
|
|
157
|
+
* // Count is a number with 1 as default value
|
|
158
|
+
* count: { type: 'number', default: 1 }
|
|
159
|
+
* },
|
|
160
|
+
* // Optional UI schema customization
|
|
161
|
+
* ui: {
|
|
162
|
+
* // Present color choices as radio buttons
|
|
163
|
+
* color: { 'ui:widget': 'radio' },
|
|
164
|
+
* // Text is a textarea with 5 rows
|
|
165
|
+
* text: { 'ui:widget': 'textarea', 'ui:options': { rows: 5 } },
|
|
166
|
+
* // Count is a number field with spinner button
|
|
167
|
+
* count: { 'ui:widget': 'updown' }
|
|
168
|
+
* },
|
|
169
|
+
* // Color and text area required properties
|
|
170
|
+
* required: ['color', 'text']
|
|
171
|
+
* })
|
|
172
|
+
*
|
|
173
|
+
*/
|
|
174
|
+
static registerComponent(component: React.ComponentType, options: ExternalComponentOptions) {
|
|
175
|
+
if (!options.name)
|
|
176
|
+
throw new Error('Could not register external component. Please make sure you provide a name in the options')
|
|
177
|
+
const id = options.name
|
|
128
178
|
const { thumbnail, group, ui: uiSchema, ...schemaOptions } = options
|
|
129
|
-
const schema = { ...schemaOptions, type: 'object' } as
|
|
130
|
-
FEAASExternal.registered[id] = {
|
|
179
|
+
const schema = { ...schemaOptions, type: 'object' } as JSONSchema
|
|
180
|
+
FEAASExternal.registered[id] = {
|
|
181
|
+
component,
|
|
182
|
+
name: id,
|
|
183
|
+
schema,
|
|
184
|
+
uiSchema,
|
|
185
|
+
thumbnail,
|
|
186
|
+
group,
|
|
187
|
+
title: schema?.title || id
|
|
188
|
+
}
|
|
131
189
|
FEAASExternal.setRegistrationCallback()
|
|
132
190
|
}
|
|
133
191
|
|
|
@@ -161,7 +219,7 @@ export default class FEAASExternal extends FEAASElement<FEAASExternalProps, Reac
|
|
|
161
219
|
|
|
162
220
|
/*static loadComponents() {
|
|
163
221
|
const components = JSON.parse(sessionStorage.getItem('feaas-external-components'))
|
|
164
|
-
components.forEach((c: { name: string; schema: any; component: React.
|
|
222
|
+
components.forEach((c: { name: string; schema: any; component: React.ComponentType }) => {
|
|
165
223
|
const name = c.name
|
|
166
224
|
const schema = c.schema
|
|
167
225
|
const component = eval('(' + c.component + ')')
|
|
@@ -192,4 +250,5 @@ declare global {
|
|
|
192
250
|
FEAASExternal.register('feaas-external')
|
|
193
251
|
FEAASExternal.setRegistrationCallback()
|
|
194
252
|
|
|
253
|
+
export const registerComponent = FEAASExternal.registerComponent
|
|
195
254
|
export { FEAASExternal as External }
|