@sitecore-feaas/clientside 0.2.2 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/build/components/FEAASComponent.js +11 -5
- package/build/components/FEAASComponent.js.map +1 -1
- package/build/components/FEAASElement.js +34 -9
- package/build/components/FEAASElement.js.map +1 -1
- package/build/dom/rendering.js +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 -1
- package/build/headless.js.map +1 -1
- package/build/ui/FEAASExternal.js +15 -13
- package/build/ui/FEAASExternal.js.map +1 -1
- package/build/ui/FEAASPicker.js +20 -5
- package/build/ui/FEAASPicker.js.map +1 -1
- package/build/ui/react.js +33 -10
- package/build/ui/react.js.map +1 -1
- package/build/{dom → utils}/data.js +3 -0
- package/build/utils/data.js.map +1 -0
- package/build/utils/datapath.js +21 -2
- package/build/utils/datapath.js.map +1 -1
- package/build/utils/settings.js +162 -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 +6 -4
- package/dist/node/index.cjs.map +4 -4
- package/dist/node/index.esm.js +6 -4
- 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 +1 -1
- package/react.cjs +8 -6
- package/react.cjs.map +4 -4
- package/react.js +8 -6
- package/react.js.map +4 -4
- package/src/components/FEAASComponent.tsx +21 -8
- package/src/components/FEAASElement.tsx +35 -8
- package/src/dom/component.ts +2 -2
- package/src/dom/html.ts +2 -2
- package/src/dom/rendering.ts +10 -7
- package/src/dom/thumbnail.ts +29 -24
- package/src/headless.ts +6 -1
- package/src/ui/FEAASExternal.tsx +15 -13
- package/src/ui/FEAASPicker.tsx +23 -6
- package/src/ui/react.tsx +53 -16
- package/src/{dom → utils}/data.ts +3 -1
- package/src/utils/datapath.ts +24 -2
- package/src/utils/settings.ts +200 -0
- package/test/FEAASComponent.react.test.tsx +1 -1
- package/test/{FEAASComponent2.test.tsx → FEAASComponentData.test.tsx} +56 -52
- package/test/ServerComponent.test.tsx +33 -0
- package/test/cdn.ts +1 -1
- package/test/datasettings.test.ts +248 -0
- package/test/{extras.ts → extras.test.ts} +1 -1
- package/test/fetch.ts +2 -2
- package/types/components/FEAASComponent.d.ts +10 -5
- package/types/components/FEAASComponent.d.ts.map +1 -1
- package/types/components/FEAASElement.d.ts +2 -0
- package/types/components/FEAASElement.d.ts.map +1 -1
- package/types/dom/component.d.ts +2 -2
- package/types/dom/component.d.ts.map +1 -1
- package/types/dom/html.d.ts +2 -2
- package/types/dom/html.d.ts.map +1 -1
- package/types/dom/rendering.d.ts +8 -7
- 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 +6 -1
- package/types/headless.d.ts.map +1 -1
- package/types/tsbuildinfo +1 -1
- package/types/ui/FEAASExternal.d.ts +15 -13
- package/types/ui/FEAASExternal.d.ts.map +1 -1
- package/types/ui/FEAASPicker.d.ts +9 -6
- package/types/ui/FEAASPicker.d.ts.map +1 -1
- package/types/ui/react.d.ts +8 -3
- package/types/ui/react.d.ts.map +1 -1
- package/types/ui/tsbuildinfo +1 -1
- package/types/{dom → utils}/data.d.ts +1 -1
- package/types/utils/data.d.ts.map +1 -0
- package/types/utils/datapath.d.ts +3 -0
- package/types/utils/datapath.d.ts.map +1 -1
- package/types/utils/settings.d.ts +89 -0
- package/types/utils/settings.d.ts.map +1 -0
- package/build/dom/data.js.map +0 -1
- package/build/utils/fetch.js +0 -26
- package/build/utils/fetch.js.map +0 -1
- package/src/utils/fetch.ts +0 -44
- package/test/cjs/tsconfig.json +0 -9
- package/test/fetch-data.ts +0 -55
- package/types/dom/data.d.ts.map +0 -1
- package/types/utils/fetch.d.ts +0 -9
- package/types/utils/fetch.d.ts.map +0 -1
|
@@ -2,7 +2,7 @@ import { DEFAULT_CDN_URL, FEAASCDNComponentParams, fetchAndRevalidateComponent,
|
|
|
2
2
|
import { renderDOMContent, renderDOMElement } from '../dom/rendering.js'
|
|
3
3
|
import { loadStylesheetAllowStale } from '../headless.js'
|
|
4
4
|
import { ControlledPromise } from '../utils/promise.js'
|
|
5
|
-
import {
|
|
5
|
+
import { DataOptions, DataSettings } from '../utils/settings.js'
|
|
6
6
|
import { FEAASElement } from './FEAASElement.js'
|
|
7
7
|
|
|
8
8
|
export function FEAASComponentsProps(element: FEAASElement) {
|
|
@@ -13,14 +13,19 @@ export function FEAASComponentsProps(element: FEAASElement) {
|
|
|
13
13
|
if (!parsed) throw new Error(`Could not parse FEAAS Component source: ${src}`)
|
|
14
14
|
var { cdn, library, component, version, revision } = parsed
|
|
15
15
|
}
|
|
16
|
+
|
|
16
17
|
const dataValue = element.getAttribute('data')
|
|
18
|
+
|
|
17
19
|
if (dataValue != null && dataValue != '') {
|
|
18
20
|
try {
|
|
19
21
|
data = typeof dataValue == 'string' ? JSON.parse(dataValue) : {}
|
|
20
|
-
} catch (e) {
|
|
22
|
+
} catch (e) {
|
|
23
|
+
console.error(e)
|
|
24
|
+
}
|
|
21
25
|
}
|
|
26
|
+
|
|
22
27
|
return {
|
|
23
|
-
data: data || {},
|
|
28
|
+
data: (data || {}) as DataOptions,
|
|
24
29
|
cdn: element.getContextAttribute('cdn') ?? cdn ?? DEFAULT_CDN_URL,
|
|
25
30
|
library: element.getAttribute('library') ?? library,
|
|
26
31
|
component: element.getAttribute('component') ?? component,
|
|
@@ -89,12 +94,13 @@ export class FEAASComponent extends FEAASElement<FEAASComponentsProps, string> {
|
|
|
89
94
|
version: 'responsive',
|
|
90
95
|
withStylesheet: true,
|
|
91
96
|
editable: false,
|
|
92
|
-
suspended: false
|
|
97
|
+
suspended: false,
|
|
98
|
+
data: {}
|
|
93
99
|
} as Partial<FEAASComponentsProps>
|
|
94
100
|
|
|
95
101
|
needsRefresh: boolean
|
|
96
102
|
|
|
97
|
-
setData(data:
|
|
103
|
+
setData(data: DataOptions) {
|
|
98
104
|
this.set({ data })
|
|
99
105
|
}
|
|
100
106
|
|
|
@@ -102,6 +108,10 @@ export class FEAASComponent extends FEAASElement<FEAASComponentsProps, string> {
|
|
|
102
108
|
return FEAASComponentsProps(this)
|
|
103
109
|
}
|
|
104
110
|
|
|
111
|
+
isJSONAttribute(prop: keyof FEAASComponentsProps) {
|
|
112
|
+
return prop == 'data'
|
|
113
|
+
}
|
|
114
|
+
|
|
105
115
|
attributeChangedCallback(name: string, oldValue: string, newValue: string): void {
|
|
106
116
|
if (oldValue === newValue) return
|
|
107
117
|
|
|
@@ -125,7 +135,8 @@ export class FEAASComponent extends FEAASElement<FEAASComponentsProps, string> {
|
|
|
125
135
|
|
|
126
136
|
this.whenDataReady = this.whenDataReady.restart()
|
|
127
137
|
|
|
128
|
-
const data = await
|
|
138
|
+
const data = await DataSettings.fetch(this.props.data)
|
|
139
|
+
|
|
129
140
|
const alreadyRendered = this.alreadyRendered
|
|
130
141
|
|
|
131
142
|
await this.whenDataReady.resolve(data)
|
|
@@ -229,7 +240,6 @@ export class FEAASComponent extends FEAASElement<FEAASComponentsProps, string> {
|
|
|
229
240
|
|
|
230
241
|
update() {
|
|
231
242
|
this.alreadyRendered = true
|
|
232
|
-
|
|
233
243
|
if (Array.from(this.children).filter((c) => c.tagName != 'BR').length > 0 && !this.needsRefresh) {
|
|
234
244
|
renderDOMElement(this, this.readyData)
|
|
235
245
|
} else {
|
|
@@ -248,6 +258,7 @@ declare global {
|
|
|
248
258
|
interface IntrinsicElements {
|
|
249
259
|
'feaas-component': {
|
|
250
260
|
data?: any
|
|
261
|
+
suppressHydrationWarning?: boolean
|
|
251
262
|
template?: string
|
|
252
263
|
instance?: string
|
|
253
264
|
editable?: boolean
|
|
@@ -257,7 +268,9 @@ declare global {
|
|
|
257
268
|
'with-stylesheet'?: 'false' | 'true' | ''
|
|
258
269
|
} & (
|
|
259
270
|
| {
|
|
260
|
-
/**
|
|
271
|
+
/**
|
|
272
|
+
* @deprecated Use cdn instead.
|
|
273
|
+
*/
|
|
261
274
|
hostname?: string
|
|
262
275
|
cdn?: string
|
|
263
276
|
library: string
|
|
@@ -118,7 +118,7 @@ export class FEAASElement<Props = any, Payload = any> extends WebComponent {
|
|
|
118
118
|
if (this.connected) return
|
|
119
119
|
this.connected = true
|
|
120
120
|
this.log('FEAAS: Mount', this.tagName, this)
|
|
121
|
-
this.
|
|
121
|
+
this.dispatchEventIndirectly(new Event('feaasMount', { bubbles: true, composed: true }))
|
|
122
122
|
this.getElements().map((element: FEAASElement) => element.mount?.())
|
|
123
123
|
this.set()
|
|
124
124
|
} catch (e) {
|
|
@@ -126,6 +126,21 @@ export class FEAASElement<Props = any, Payload = any> extends WebComponent {
|
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
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
|
+
|
|
140
|
+
isJSONAttribute(attribute: keyof Props) {
|
|
141
|
+
return false
|
|
142
|
+
}
|
|
143
|
+
|
|
129
144
|
log(...args: any) {
|
|
130
145
|
if (typeof process != 'undefined' && process.env.TESTING) return
|
|
131
146
|
console.log(...args)
|
|
@@ -135,7 +150,7 @@ export class FEAASElement<Props = any, Payload = any> extends WebComponent {
|
|
|
135
150
|
unmount() {
|
|
136
151
|
if (!this.connected) return
|
|
137
152
|
this.log('FEAAS: Unmount', this)
|
|
138
|
-
this.
|
|
153
|
+
this.dispatchEventIndirectly(new Event('feaasUnmount', { bubbles: true, composed: true }))
|
|
139
154
|
this.connected = false
|
|
140
155
|
}
|
|
141
156
|
|
|
@@ -182,7 +197,7 @@ export class FEAASElement<Props = any, Payload = any> extends WebComponent {
|
|
|
182
197
|
const index = this.descendants.indexOf(target)
|
|
183
198
|
if (index == -1) return
|
|
184
199
|
this.log('FEAAS: Unnest', this, target)
|
|
185
|
-
this.descendants.splice(index,
|
|
200
|
+
this.descendants.splice(index, 1)
|
|
186
201
|
this.refs[target.getComponentName()] = null
|
|
187
202
|
if (target.refs.parent == this) target.refs.parent = null
|
|
188
203
|
if (target.refs.top == this) {
|
|
@@ -224,7 +239,7 @@ export class FEAASElement<Props = any, Payload = any> extends WebComponent {
|
|
|
224
239
|
/** Generic error handler */
|
|
225
240
|
onError(error: Error, where: string = 'uncaught', handled = false) {
|
|
226
241
|
const event = new CustomEvent('feaasError', { bubbles: true, composed: true, detail: error })
|
|
227
|
-
this.
|
|
242
|
+
this.dispatchEventIndirectly(event)
|
|
228
243
|
this.whenLoaded.reject(error)
|
|
229
244
|
if (!event.defaultPrevented && !handled) {
|
|
230
245
|
this.whenRendered.reject(error)
|
|
@@ -248,7 +263,7 @@ export class FEAASElement<Props = any, Payload = any> extends WebComponent {
|
|
|
248
263
|
if (this.payload === undefined) {
|
|
249
264
|
if (!this.isReadyToLoad()) return
|
|
250
265
|
this.payload = null
|
|
251
|
-
this.
|
|
266
|
+
this.dispatchEventIndirectly(new Event('feaasLoad', { bubbles: true, composed: true }))
|
|
252
267
|
const loaded = await this.load().catch((e) => {
|
|
253
268
|
this.payload = undefined
|
|
254
269
|
throw e
|
|
@@ -272,6 +287,7 @@ export class FEAASElement<Props = any, Payload = any> extends WebComponent {
|
|
|
272
287
|
}
|
|
273
288
|
|
|
274
289
|
flush() {
|
|
290
|
+
if (!this.connected) return
|
|
275
291
|
const flushSync = (this.payload as ReactPayload)?.ReactDOM?.flushSync
|
|
276
292
|
if (flushSync) {
|
|
277
293
|
flushSync(() => this.update())
|
|
@@ -433,12 +449,23 @@ export class FEAASElement<Props = any, Payload = any> extends WebComponent {
|
|
|
433
449
|
...this.overrides
|
|
434
450
|
}
|
|
435
451
|
for (var property in this.props) {
|
|
452
|
+
const isJSON = this.isJSONAttribute(property)
|
|
436
453
|
const value = this.props[property]
|
|
454
|
+
const valueAsString = isJSON ? JSON.stringify(value) : String(value)
|
|
455
|
+
const defaultValue = this.defaultProps[property as keyof typeof this.defaultProps]
|
|
456
|
+
const defaultValueAsString = isJSON ? JSON.stringify(defaultValue) : String(defaultValue)
|
|
437
457
|
const attribute = property.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
|
|
438
|
-
|
|
458
|
+
const oldValue = this.getAttribute(attribute)
|
|
459
|
+
try {
|
|
460
|
+
var odlValueAsString: string = isJSON ? JSON.stringify(JSON.parse(oldValue)) : oldValue
|
|
461
|
+
} catch (e) {
|
|
462
|
+
var odlValueAsString: string = null
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (value == null || valueAsString === defaultValueAsString) {
|
|
439
466
|
this.removeAttribute(attribute)
|
|
440
|
-
} else if (typeof value == 'string' || typeof value == 'number' || typeof value == 'boolean') {
|
|
441
|
-
if (
|
|
467
|
+
} else if (typeof value == 'string' || typeof value == 'number' || typeof value == 'boolean' || isJSON) {
|
|
468
|
+
if (odlValueAsString != valueAsString) this.setAttribute(attribute, valueAsString)
|
|
442
469
|
}
|
|
443
470
|
}
|
|
444
471
|
} catch (e) {
|
package/src/dom/component.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { fetchAndRevalidateComponent } from '../cdn.js'
|
|
2
|
-
import {
|
|
2
|
+
import { DataScopes } from '../utils/settings.js'
|
|
3
3
|
import { FEAASComponentProps, renderDOMContent, renderDOMElement } from './rendering.js'
|
|
4
4
|
|
|
5
|
-
export function renderComponentElement(content: string, datasources?:
|
|
5
|
+
export function renderComponentElement(content: string, datasources?: DataScopes) {
|
|
6
6
|
const wrapper = document.createElement('div')
|
|
7
7
|
wrapper.innerHTML = content
|
|
8
8
|
const fragment = document.createDocumentFragment()
|
package/src/dom/html.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Lightweight DOM parser and serializer to use in server environments */
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { DataScopes } from '../utils/settings.js'
|
|
4
4
|
import { FEAASCustomizations, renderDOMElement } from './rendering.js'
|
|
5
5
|
|
|
6
6
|
type HTMLAttribute = { name: string; value: string }
|
|
@@ -323,7 +323,7 @@ export function serializeHTML(nodes: HTMLNode[]): string {
|
|
|
323
323
|
.join('')
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
-
export function renderHTMLContent(template: string, data?:
|
|
326
|
+
export function renderHTMLContent(template: string, data?: DataScopes, config?: FEAASCustomizations) {
|
|
327
327
|
const newTemplate = config?.processTemplate?.(null, template, data) || template
|
|
328
328
|
const nodes = parseHTML(newTemplate)
|
|
329
329
|
return renderDOMElement(nodes[0] as unknown as HTMLElement, data, config).outerHTML
|
package/src/dom/rendering.ts
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
import { FEAASCDNComponentProps } from '../cdn.js'
|
|
2
|
-
import {
|
|
2
|
+
import { queryScopes, serializeCH1RichText } from '../utils/data.js'
|
|
3
|
+
import { Data, DataScopes } from '../utils/settings.js'
|
|
3
4
|
import { parseHTML } from './html.js'
|
|
4
5
|
import { loadScript } from './scripts.js'
|
|
5
6
|
import { normalizeCollectionScope } from '../utils/datapath.js'
|
|
7
|
+
import { DataOptions } from '../headless.js'
|
|
6
8
|
|
|
7
9
|
export interface FEAASCustomizations {
|
|
8
|
-
processTemplate?: (element: HTMLElement, template: string, data:
|
|
9
|
-
processData?: (element: HTMLElement, data:
|
|
10
|
+
processTemplate?: (element: HTMLElement, template: string, data: DataScopes) => string
|
|
11
|
+
processData?: (element: HTMLElement, data: DataScopes) => any
|
|
10
12
|
processValue?: (element: HTMLElement, property: string, value: any) => any
|
|
11
13
|
shouldRepeat?: (element: HTMLElement, value: any) => boolean
|
|
12
|
-
onRender?: (element: HTMLElement, data:
|
|
14
|
+
onRender?: (element: HTMLElement, data: DataScopes) => void
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
export interface ExplicitTemplate {
|
|
16
18
|
template: string
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
export type FEAASComponentProps<T =
|
|
21
|
+
export type FEAASComponentProps<T = DataOptions> = Partial<ExplicitTemplate> & {
|
|
20
22
|
data?: T
|
|
21
23
|
} & (ExplicitTemplate | FEAASCDNComponentProps) &
|
|
22
24
|
FEAASCustomizations
|
|
@@ -192,11 +194,12 @@ export function renderDOMElement(
|
|
|
192
194
|
}
|
|
193
195
|
return element
|
|
194
196
|
}
|
|
197
|
+
|
|
195
198
|
function isElement(node: Node): node is HTMLElement {
|
|
196
199
|
return node.nodeType == 1
|
|
197
200
|
}
|
|
198
201
|
|
|
199
|
-
export function renderDOMContent(root: HTMLElement, template: string, data?:
|
|
202
|
+
export function renderDOMContent(root: HTMLElement, template: string, data?: Data, config?: FEAASCustomizations) {
|
|
200
203
|
const newTemplate = config?.processTemplate?.(root, template, data) || template
|
|
201
204
|
root.innerHTML = newTemplate
|
|
202
205
|
root.classList.add('-feaas')
|
|
@@ -212,7 +215,7 @@ export function renderDOMContent(root: HTMLElement, template: string, data?: Dat
|
|
|
212
215
|
return renderDOMElement(root, data, config)
|
|
213
216
|
}
|
|
214
217
|
|
|
215
|
-
export function renderHTMLContent(template: string, data?:
|
|
218
|
+
export function renderHTMLContent(template: string, data?: Data, config?: FEAASCustomizations, fakeDOM = false) {
|
|
216
219
|
const newTemplate = config?.processTemplate?.(null, template, data) || template
|
|
217
220
|
if (typeof document === 'undefined' || fakeDOM) {
|
|
218
221
|
const nodes = parseHTML(newTemplate)
|
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
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import FEAASReactElement from './components/FEAASReactElement.js'
|
|
7
7
|
|
|
8
8
|
export * from './dom/rendering.js'
|
|
9
|
-
export * from './
|
|
9
|
+
export * from './utils/data.js'
|
|
10
10
|
export * from './dom/scripts.js'
|
|
11
11
|
export * from './dom/stylesheet.js'
|
|
12
12
|
export * from './dom/component.js'
|
|
@@ -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 './utils/data.js'
|
|
24
|
+
export * as DataPath from './utils/datapath.js'
|
|
25
|
+
export { DataSettings, fetchData } from './utils/settings.js'
|
|
26
|
+
export type { Data, DataOptionsById, DataOptions, DataScopes } from './utils/settings.js'
|
|
22
27
|
export * as Thumbnail from './dom/thumbnail.js'
|
|
23
28
|
|
|
24
29
|
declare global {
|
package/src/ui/FEAASExternal.tsx
CHANGED
|
@@ -122,10 +122,23 @@ export default class FEAASExternal extends FEAASElement<FEAASExternalProps, Reac
|
|
|
122
122
|
|
|
123
123
|
/**
|
|
124
124
|
* Register React component to be renderable as Sitecore component (in Components and Pages). Properties are defined
|
|
125
|
-
* as JSON
|
|
126
|
-
*
|
|
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.
|
|
127
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
|
|
128
140
|
* @example
|
|
141
|
+
*
|
|
129
142
|
* FEAAS.External.registerComponent(MyComponent, {
|
|
130
143
|
* // Name of a component as it will be presented to the user
|
|
131
144
|
* name: 'My Component',
|
|
@@ -157,17 +170,6 @@ export default class FEAASExternal extends FEAASElement<FEAASExternalProps, Reac
|
|
|
157
170
|
* required: ['color', 'text']
|
|
158
171
|
* })
|
|
159
172
|
*
|
|
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
173
|
*/
|
|
172
174
|
static registerComponent(component: React.ComponentType, options: ExternalComponentOptions) {
|
|
173
175
|
if (!options.name)
|
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 '
|
|
8
|
+
mode: element?.getAttribute('mode') as FEAASPickerImports.PickerProps['mode'],
|
|
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',
|
|
@@ -13,7 +13,8 @@ export function FEAASPickerProps(element: FEAASElement<any, any>) {
|
|
|
13
13
|
variant: element?.getAttribute('variant') as 'internal' | 'pages',
|
|
14
14
|
context: undefined as FEAASPickerImports.PickerProps['context'],
|
|
15
15
|
onClose: undefined as (...args: any[]) => void,
|
|
16
|
-
|
|
16
|
+
setAttribute: undefined as FEAASPickerImports.AttributeSetter,
|
|
17
|
+
getAttribute: undefined as FEAASPickerImports.AttributeGetter
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
export type FEAASPickerProps = ReturnType<typeof FEAASPickerProps>
|
|
@@ -40,21 +41,37 @@ export class FEAASPicker extends FEAASElement<FEAASPickerProps, typeof FEAASPick
|
|
|
40
41
|
{ placeholder, context, mode, children, variant }: React.PropsWithChildren<FEAASPickerProps>,
|
|
41
42
|
{ Picker, React, useLibrary }: typeof FEAASPickerImports
|
|
42
43
|
) {
|
|
44
|
+
const [renders, forceRender] = React.useReducer((c) => c + 1, 0)
|
|
43
45
|
const collections = useLibrary('collections')
|
|
46
|
+
const components = useLibrary('components')
|
|
44
47
|
const rules = useLibrary('stylesheet.styles')
|
|
48
|
+
const component = components.find((c) => c.id == context?.getAttribute('component'))
|
|
49
|
+
var settings = context?.getAttribute('data')
|
|
50
|
+
if (settings) {
|
|
51
|
+
try {
|
|
52
|
+
settings = JSON.parse(settings)
|
|
53
|
+
} catch (e) {}
|
|
54
|
+
}
|
|
45
55
|
return (
|
|
46
56
|
<Picker
|
|
47
57
|
rules={rules}
|
|
48
58
|
collections={collections}
|
|
49
59
|
themeContext={context}
|
|
50
60
|
placeholder={placeholder}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
61
|
+
component={component}
|
|
62
|
+
data={settings}
|
|
63
|
+
versionId={context?.getAttribute('version')}
|
|
64
|
+
mode={mode || (component ? 'component' : null)}
|
|
65
|
+
setAttribute={(context, attribute, value) => {
|
|
66
|
+
if (attribute == 'data' && value) value = JSON.stringify(value)
|
|
67
|
+
this.props.setAttribute(context, attribute, value)
|
|
68
|
+
forceRender()
|
|
54
69
|
}}
|
|
55
|
-
onContextAttributeSet={this.props.onContextAttributeSet}
|
|
56
70
|
context={context}
|
|
57
71
|
variant={variant}
|
|
72
|
+
onModeChange={(mode) => {
|
|
73
|
+
this.set({ mode })
|
|
74
|
+
}}
|
|
58
75
|
>
|
|
59
76
|
{children}
|
|
60
77
|
</Picker>
|
package/src/ui/react.tsx
CHANGED
|
@@ -1,33 +1,70 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import {
|
|
2
|
+
FEAASComponentProps,
|
|
3
|
+
fetchComponent,
|
|
4
|
+
getComponentURL,
|
|
5
|
+
getStylesheetURL,
|
|
6
|
+
parseComponentSource
|
|
7
|
+
} from './index.js'
|
|
5
8
|
import React from 'react'
|
|
6
9
|
import { renderHTMLContent } from '../dom/html.js'
|
|
10
|
+
import { DataSettings } from '../utils/settings.js'
|
|
7
11
|
|
|
8
12
|
export { renderHTMLContent }
|
|
9
13
|
export { External } from './FEAASExternal.js'
|
|
14
|
+
export * from './index.js'
|
|
15
|
+
export * from '../headless.js'
|
|
16
|
+
|
|
17
|
+
export const ServerComponent = async (props: FEAASComponentProps) => {
|
|
18
|
+
const fetchedData = await DataSettings.fetch(props?.data || {})
|
|
19
|
+
|
|
20
|
+
const p = { ...props }
|
|
21
|
+
|
|
22
|
+
if (('src' in p || 'component' in p) && p?.template == null) {
|
|
23
|
+
const response = await fetchComponent(p)
|
|
24
|
+
|
|
25
|
+
p.template = response.template
|
|
26
|
+
// p.lastModified = response.lastModified
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return FEAASReactComponent({ ...p, data: fetchedData })
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function FEAASReactComponent({ data, ...props }: FEAASComponentProps & { src?: string; preload?: boolean }) {
|
|
33
|
+
let html = ''
|
|
34
|
+
let preload = null
|
|
10
35
|
|
|
11
|
-
export function FEAASEmbed({ data, ...props }: FEAASComponentProps<any>) {
|
|
12
36
|
if ('template' in props && props.template) {
|
|
13
|
-
|
|
37
|
+
html = renderHTMLContent(props.template, data || {})
|
|
14
38
|
}
|
|
15
39
|
|
|
16
40
|
if ('src' in props) {
|
|
17
|
-
props = {
|
|
18
|
-
|
|
19
|
-
|
|
41
|
+
props = { ...parseComponentSource(props.src), ...props }
|
|
42
|
+
} else if (!('template' in props)) {
|
|
43
|
+
if (!props.version) {
|
|
44
|
+
props = {
|
|
45
|
+
...props,
|
|
46
|
+
version: 'responsive'
|
|
47
|
+
}
|
|
20
48
|
}
|
|
21
49
|
}
|
|
22
50
|
|
|
23
51
|
return (
|
|
24
|
-
|
|
25
|
-
{
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
52
|
+
<>
|
|
53
|
+
{'preload' in props && preload === true && 'src' in props && (
|
|
54
|
+
<>
|
|
55
|
+
<link rel='preload' as='fetch' href={getComponentURL(props.src)} crossOrigin='anonymous' />
|
|
56
|
+
<link rel='preload' as='style' href={getStylesheetURL(props.src)} crossOrigin='anonymous' />
|
|
57
|
+
</>
|
|
58
|
+
)}
|
|
59
|
+
<feaas-component
|
|
60
|
+
{...props}
|
|
61
|
+
suppressHydrationWarning={true}
|
|
62
|
+
template={null}
|
|
63
|
+
data={typeof data == 'string' ? data : data ? JSON.stringify(data) : null}
|
|
64
|
+
dangerouslySetInnerHTML={{ __html: html || '' }}
|
|
65
|
+
/>
|
|
66
|
+
</>
|
|
30
67
|
)
|
|
31
68
|
}
|
|
32
69
|
|
|
33
|
-
export const Component =
|
|
70
|
+
export const Component = FEAASReactComponent
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { JSONPath } from 'jsonpath-plus'
|
|
2
2
|
|
|
3
|
-
export
|
|
3
|
+
export function queryObject(json: any, path: string) {
|
|
4
|
+
return JSONPath({ path, json })
|
|
5
|
+
}
|
|
4
6
|
|
|
5
7
|
export function queryScopes(scopes: any, datapath: string, singleValue = false, json: any = scopes) {
|
|
6
8
|
var path = datapath
|
package/src/utils/datapath.ts
CHANGED
|
@@ -20,12 +20,34 @@ export const parseRange = (qualifier: string) => {
|
|
|
20
20
|
return bits.join(',')
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
export const getQualifier = (jsonpath: string) => jsonpath?.match(/(\.\*|\[[^\]]
|
|
23
|
+
export const getQualifier = (jsonpath: string) => jsonpath?.match(/(\.\*|\[[^\]]*\])$/)?.[0]
|
|
24
24
|
|
|
25
25
|
export function cleanCollectionBit(path: string) {
|
|
26
|
-
return path.replace(/(
|
|
26
|
+
return path.replace(/(\.?\[[^\]\[]*\]|\.\*)$/g, '')
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export function normalizeCollectionScope(path: string) {
|
|
30
30
|
return cleanCollectionBit(path) + '.*'
|
|
31
31
|
}
|
|
32
|
+
|
|
33
|
+
// increment numbers by given numbers, exclude second value of ranges to ensure inclusivity
|
|
34
|
+
export function adjustNumbers(string: string, diff: number) {
|
|
35
|
+
return string?.replace(/(.|^)(\d+)/g, (m, b, n) => b + (b == ':' ? n : String(parseInt(n) + diff)))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function getHumanizedLabel(path: string, maxBits = 2) {
|
|
39
|
+
var bits = cleanCollectionBit(path)
|
|
40
|
+
.split(/]\.|\.\*\./g)
|
|
41
|
+
.pop()
|
|
42
|
+
.split(/\./g)
|
|
43
|
+
.slice(-3)
|
|
44
|
+
bits = bits.filter((b) => isNaN(parseInt(b)))
|
|
45
|
+
return bits
|
|
46
|
+
.map((bit) => bit.charAt(0).toUpperCase() + bit.slice(1))
|
|
47
|
+
.slice(-maxBits)
|
|
48
|
+
.join(' ')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function getLastPathBit(path: string) {
|
|
52
|
+
return cleanCollectionBit(path).split('.').pop()
|
|
53
|
+
}
|