@sitecore-feaas/clientside 0.2.2 → 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 +1 -1
- package/build/components/FEAASComponent.js +3 -1
- package/build/components/FEAASComponent.js.map +1 -1
- package/build/components/FEAASElement.js +16 -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/FEAASExternal.js +15 -13
- package/build/ui/FEAASExternal.js.map +1 -1
- package/build/ui/FEAASPicker.js +11 -5
- package/build/ui/FEAASPicker.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 +16 -2
- package/build/utils/fetch.js.map +1 -1
- 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 +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 +10 -3
- package/src/components/FEAASElement.tsx +17 -5
- package/src/dom/rendering.ts +1 -0
- package/src/dom/thumbnail.ts +29 -24
- package/src/headless.ts +5 -0
- package/src/ui/FEAASExternal.tsx +15 -13
- package/src/ui/FEAASPicker.tsx +15 -6
- package/src/ui/react.tsx +45 -16
- package/src/utils/datapath.ts +17 -0
- package/src/utils/fetch.ts +20 -4
- package/src/utils/settings.ts +69 -0
- package/test/FEAASComponent.react.test.tsx +1 -1
- package/test/ServerComponent.test.tsx +33 -0
- package/test/settings.test.ts +78 -0
- package/types/components/FEAASComponent.d.ts +3 -1
- package/types/components/FEAASComponent.d.ts.map +1 -1
- package/types/components/FEAASElement.d.ts +1 -0
- 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/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 +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.map +1 -1
- package/types/utils/settings.d.ts +13 -0
- package/types/utils/settings.d.ts.map +1 -0
- /package/test/{FEAASComponent2.test.tsx → FEAASComponentData.test.tsx} +0 -0
|
@@ -13,12 +13,17 @@ 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
28
|
data: data || {},
|
|
24
29
|
cdn: element.getContextAttribute('cdn') ?? cdn ?? DEFAULT_CDN_URL,
|
|
@@ -126,6 +131,7 @@ export class FEAASComponent extends FEAASElement<FEAASComponentsProps, string> {
|
|
|
126
131
|
this.whenDataReady = this.whenDataReady.restart()
|
|
127
132
|
|
|
128
133
|
const data = await fetchData(this.props.data)
|
|
134
|
+
|
|
129
135
|
const alreadyRendered = this.alreadyRendered
|
|
130
136
|
|
|
131
137
|
await this.whenDataReady.resolve(data)
|
|
@@ -229,7 +235,6 @@ export class FEAASComponent extends FEAASElement<FEAASComponentsProps, string> {
|
|
|
229
235
|
|
|
230
236
|
update() {
|
|
231
237
|
this.alreadyRendered = true
|
|
232
|
-
|
|
233
238
|
if (Array.from(this.children).filter((c) => c.tagName != 'BR').length > 0 && !this.needsRefresh) {
|
|
234
239
|
renderDOMElement(this, this.readyData)
|
|
235
240
|
} else {
|
|
@@ -257,7 +262,9 @@ declare global {
|
|
|
257
262
|
'with-stylesheet'?: 'false' | 'true' | ''
|
|
258
263
|
} & (
|
|
259
264
|
| {
|
|
260
|
-
/**
|
|
265
|
+
/**
|
|
266
|
+
* @deprecated Use cdn instead.
|
|
267
|
+
*/
|
|
261
268
|
hostname?: string
|
|
262
269
|
cdn?: string
|
|
263
270
|
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,17 @@ 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
|
+
|
|
129
140
|
log(...args: any) {
|
|
130
141
|
if (typeof process != 'undefined' && process.env.TESTING) return
|
|
131
142
|
console.log(...args)
|
|
@@ -135,7 +146,7 @@ export class FEAASElement<Props = any, Payload = any> extends WebComponent {
|
|
|
135
146
|
unmount() {
|
|
136
147
|
if (!this.connected) return
|
|
137
148
|
this.log('FEAAS: Unmount', this)
|
|
138
|
-
this.
|
|
149
|
+
this.dispatchEventIndirectly(new Event('feaasUnmount', { bubbles: true, composed: true }))
|
|
139
150
|
this.connected = false
|
|
140
151
|
}
|
|
141
152
|
|
|
@@ -182,7 +193,7 @@ export class FEAASElement<Props = any, Payload = any> extends WebComponent {
|
|
|
182
193
|
const index = this.descendants.indexOf(target)
|
|
183
194
|
if (index == -1) return
|
|
184
195
|
this.log('FEAAS: Unnest', this, target)
|
|
185
|
-
this.descendants.splice(index,
|
|
196
|
+
this.descendants.splice(index, 1)
|
|
186
197
|
this.refs[target.getComponentName()] = null
|
|
187
198
|
if (target.refs.parent == this) target.refs.parent = null
|
|
188
199
|
if (target.refs.top == this) {
|
|
@@ -224,7 +235,7 @@ export class FEAASElement<Props = any, Payload = any> extends WebComponent {
|
|
|
224
235
|
/** Generic error handler */
|
|
225
236
|
onError(error: Error, where: string = 'uncaught', handled = false) {
|
|
226
237
|
const event = new CustomEvent('feaasError', { bubbles: true, composed: true, detail: error })
|
|
227
|
-
this.
|
|
238
|
+
this.dispatchEventIndirectly(event)
|
|
228
239
|
this.whenLoaded.reject(error)
|
|
229
240
|
if (!event.defaultPrevented && !handled) {
|
|
230
241
|
this.whenRendered.reject(error)
|
|
@@ -248,7 +259,7 @@ export class FEAASElement<Props = any, Payload = any> extends WebComponent {
|
|
|
248
259
|
if (this.payload === undefined) {
|
|
249
260
|
if (!this.isReadyToLoad()) return
|
|
250
261
|
this.payload = null
|
|
251
|
-
this.
|
|
262
|
+
this.dispatchEventIndirectly(new Event('feaasLoad', { bubbles: true, composed: true }))
|
|
252
263
|
const loaded = await this.load().catch((e) => {
|
|
253
264
|
this.payload = undefined
|
|
254
265
|
throw e
|
|
@@ -272,6 +283,7 @@ export class FEAASElement<Props = any, Payload = any> extends WebComponent {
|
|
|
272
283
|
}
|
|
273
284
|
|
|
274
285
|
flush() {
|
|
286
|
+
if (!this.connected) return
|
|
275
287
|
const flushSync = (this.payload as ReactPayload)?.ReactDOM?.flushSync
|
|
276
288
|
if (flushSync) {
|
|
277
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/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,29 @@ 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'))
|
|
45
49
|
return (
|
|
46
50
|
<Picker
|
|
47
51
|
rules={rules}
|
|
48
52
|
collections={collections}
|
|
49
53
|
themeContext={context}
|
|
50
54
|
placeholder={placeholder}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
55
|
+
component={component}
|
|
56
|
+
versionId={context.getAttribute('version')}
|
|
57
|
+
mode={mode || 'component'}
|
|
58
|
+
setAttribute={(context, attribute, value) => {
|
|
59
|
+
this.props.setAttribute(context, attribute, value)
|
|
60
|
+
forceRender()
|
|
54
61
|
}}
|
|
55
|
-
onContextAttributeSet={this.props.onContextAttributeSet}
|
|
56
62
|
context={context}
|
|
57
63
|
variant={variant}
|
|
64
|
+
onModeChange={(mode) => {
|
|
65
|
+
this.set({ mode })
|
|
66
|
+
}}
|
|
58
67
|
>
|
|
59
68
|
{children}
|
|
60
69
|
</Picker>
|
package/src/ui/react.tsx
CHANGED
|
@@ -1,32 +1,61 @@
|
|
|
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 { fetchData } from '../utils/fetch.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<any>) => {
|
|
18
|
+
const fetchedData = await fetchData(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 FEAASEmbed({ ...p, data: fetchedData })
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function FEAASEmbed({ data, ...props }: FEAASComponentProps<any> & { 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
|
-
...parseComponentSource(props.src),
|
|
19
|
-
...props
|
|
20
|
-
}
|
|
41
|
+
props = { ...parseComponentSource(props.src), ...props }
|
|
21
42
|
}
|
|
22
43
|
|
|
23
44
|
return (
|
|
24
|
-
|
|
25
|
-
{
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
45
|
+
<>
|
|
46
|
+
{'preload' in props && preload === true && 'src' in props && (
|
|
47
|
+
<>
|
|
48
|
+
<link rel='preload' as='fetch' href={getComponentURL(props.src)} crossOrigin='anonymous' />
|
|
49
|
+
<link rel='preload' as='style' href={getStylesheetURL(props.src)} crossOrigin='anonymous' />
|
|
50
|
+
</>
|
|
51
|
+
)}
|
|
52
|
+
<feaas-component
|
|
53
|
+
{...props}
|
|
54
|
+
template={null}
|
|
55
|
+
data={typeof data == 'string' ? data : data ? JSON.stringify(data) : null}
|
|
56
|
+
dangerouslySetInnerHTML={{ __html: html || '' }}
|
|
57
|
+
/>
|
|
58
|
+
</>
|
|
30
59
|
)
|
|
31
60
|
}
|
|
32
61
|
|
package/src/utils/datapath.ts
CHANGED
|
@@ -29,3 +29,20 @@ export function cleanCollectionBit(path: string) {
|
|
|
29
29
|
export function normalizeCollectionScope(path: string) {
|
|
30
30
|
return cleanCollectionBit(path) + '.*'
|
|
31
31
|
}
|
|
32
|
+
|
|
33
|
+
export function getHumanizedLabel(path: string, maxBits = 2) {
|
|
34
|
+
var bits = cleanCollectionBit(path)
|
|
35
|
+
.split(/]\.|\.\*\./g)
|
|
36
|
+
.pop()
|
|
37
|
+
.split(/\./g)
|
|
38
|
+
.slice(-3)
|
|
39
|
+
bits = bits.filter((b) => isNaN(parseInt(b)))
|
|
40
|
+
return bits
|
|
41
|
+
.map((bit) => bit.charAt(0).toUpperCase() + bit.slice(1))
|
|
42
|
+
.slice(-maxBits)
|
|
43
|
+
.join(' ')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function getLastPathBit(path: string) {
|
|
47
|
+
return cleanCollectionBit(path).split('.').pop()
|
|
48
|
+
}
|
package/src/utils/fetch.ts
CHANGED
|
@@ -2,8 +2,26 @@ type Data = object | any[]
|
|
|
2
2
|
|
|
3
3
|
type DataSettings = { jsonpath: string; url: string }
|
|
4
4
|
|
|
5
|
-
export const isDataSettings = (data: any): data is DataSettings =>
|
|
6
|
-
typeof data
|
|
5
|
+
export const isDataSettings = (data: any): data is DataSettings => {
|
|
6
|
+
if (typeof data.url != 'string') return false
|
|
7
|
+
|
|
8
|
+
for (var property in data) {
|
|
9
|
+
if (
|
|
10
|
+
property == 'body' ||
|
|
11
|
+
property == 'params' ||
|
|
12
|
+
property == 'headers' ||
|
|
13
|
+
property == 'url' ||
|
|
14
|
+
property === 'method' ||
|
|
15
|
+
(property == 'jsonpath' && typeof data[property] == 'string')
|
|
16
|
+
) {
|
|
17
|
+
continue
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return false
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return true
|
|
24
|
+
}
|
|
7
25
|
|
|
8
26
|
const performFetch = async (
|
|
9
27
|
settings: Parameters<typeof fetch>[1] & {
|
|
@@ -24,8 +42,6 @@ const performFetch = async (
|
|
|
24
42
|
export async function fetchData(data: DataSettings | Record<string, DataSettings | Data> | Data): Promise<Data> {
|
|
25
43
|
if (!data) return data
|
|
26
44
|
|
|
27
|
-
console.log(data)
|
|
28
|
-
|
|
29
45
|
if (isDataSettings(data)) return performFetch(data)
|
|
30
46
|
|
|
31
47
|
const keys = Object.keys(data)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export function clean(settings: any) {
|
|
2
|
+
let cleanSettings = {}
|
|
3
|
+
|
|
4
|
+
const hasNoBody =
|
|
5
|
+
settings?.method === 'GET' || settings?.method === 'HEAD' || Object.keys(settings?.body || {}).length === 0
|
|
6
|
+
|
|
7
|
+
const hasNoHeaders =
|
|
8
|
+
settings.headers &&
|
|
9
|
+
(Object.keys(settings.headers).length === 0 ||
|
|
10
|
+
(Object.keys(settings.headers).length === 1 &&
|
|
11
|
+
(settings.headers['Content-type'] || settings.headers['Content-Type']) &&
|
|
12
|
+
(settings.headers['Content-type'] === 'application/json' ||
|
|
13
|
+
settings.headers['Content-Type'] === 'application/json')))
|
|
14
|
+
|
|
15
|
+
const hasNoParams = settings.params || Object.keys(settings.params || {}).length === 0
|
|
16
|
+
|
|
17
|
+
if (settings.method && settings.method !== 'GET') cleanSettings = { ...cleanSettings, method: settings.method }
|
|
18
|
+
if (settings.body && !hasNoBody) cleanSettings = { ...cleanSettings, body: settings.body }
|
|
19
|
+
cleanSettings = {
|
|
20
|
+
...cleanSettings,
|
|
21
|
+
headers: hasNoHeaders ? { 'Content-type': 'application/json' } : settings.headers
|
|
22
|
+
}
|
|
23
|
+
if (settings.params && !hasNoParams) cleanSettings = { ...cleanSettings, params: settings.params }
|
|
24
|
+
|
|
25
|
+
return { ...cleanSettings, url: settings.url || '' }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function toFetchArguments(settings?: any): Parameters<typeof fetch> {
|
|
29
|
+
const normalized = normalize(settings)
|
|
30
|
+
|
|
31
|
+
let body = null
|
|
32
|
+
|
|
33
|
+
const contentType = normalized.headers['Content-Type'] || normalized.headers['Content-type']
|
|
34
|
+
|
|
35
|
+
const isJson = contentType === 'application/json'
|
|
36
|
+
const isForm = contentType === 'multipart/form-data'
|
|
37
|
+
const isUrlEncoded = contentType === 'application/x-www-form-urlencoded'
|
|
38
|
+
|
|
39
|
+
if (isJson) body = JSON.stringify(normalized.body)
|
|
40
|
+
if (isForm)
|
|
41
|
+
body = Object.keys(normalized.body).reduce((form: any, key) => {
|
|
42
|
+
form.append(key, normalized.body[key])
|
|
43
|
+
return form
|
|
44
|
+
}, new FormData())
|
|
45
|
+
if (isUrlEncoded) body = new URLSearchParams(normalized.body)
|
|
46
|
+
|
|
47
|
+
const params = Object.keys(normalized.params).length ? `?${new URLSearchParams(normalized.params)}` : ''
|
|
48
|
+
|
|
49
|
+
let url = `${normalized.url}${params}`
|
|
50
|
+
|
|
51
|
+
let options: any = {}
|
|
52
|
+
|
|
53
|
+
if (body) options.body = body
|
|
54
|
+
if (Object.keys(normalized.headers).length) options.headers = normalized.headers
|
|
55
|
+
if (normalized.method !== 'GET') options.method = normalized.method
|
|
56
|
+
|
|
57
|
+
return [url, options]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function normalize(settings: any) {
|
|
61
|
+
return {
|
|
62
|
+
params: settings?.params || {},
|
|
63
|
+
headers: settings?.headers || {},
|
|
64
|
+
jsonpath: settings?.jsonpath || '$',
|
|
65
|
+
method: settings?.method || 'GET',
|
|
66
|
+
body: settings?.body || {},
|
|
67
|
+
url: settings?.url || ''
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -3,7 +3,7 @@ import ReactDOM from 'react-dom'
|
|
|
3
3
|
import { describe, expect, it } from 'vitest'
|
|
4
4
|
import { Component } from '../src/ui/react.js'
|
|
5
5
|
|
|
6
|
-
async function test(react: any, html: string) {
|
|
6
|
+
export async function test(react: any, html: string) {
|
|
7
7
|
const div = document.createElement('div')
|
|
8
8
|
document.body.appendChild(div)
|
|
9
9
|
const root = ReactDOM.render(react, div)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
3
|
+
import { ServerComponent } from '../src/ui/react.js'
|
|
4
|
+
import { FEAASComponent } from '../src/components/FEAASComponent.js'
|
|
5
|
+
import {test} from './FEAASComponent.react.test.js'
|
|
6
|
+
|
|
7
|
+
const earliest = new Date(2000, 1, 1, 0, 0, 0)
|
|
8
|
+
const earlier = new Date(2000, 1, 2, 0, 0, 0)
|
|
9
|
+
const now = new Date(2000, 1, 2, 12, 0, 0)
|
|
10
|
+
|
|
11
|
+
describe('React Server', () => {
|
|
12
|
+
beforeAll(() => {
|
|
13
|
+
vi.setSystemTime(now)
|
|
14
|
+
FEAASComponent.register('feaas-component')
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
document.write()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('render server component', async () => {
|
|
22
|
+
vi.spyOn(global, 'fetch').mockImplementation(
|
|
23
|
+
async (url: string, options: Parameters<typeof fetch>[1] = {}) => new Response('{"data": "1"}')
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
const Component = await ServerComponent({ src: 'library/test', data: { url: 'http://localhost:4000/api/data' } })
|
|
27
|
+
|
|
28
|
+
await test(
|
|
29
|
+
<>{Component}</>,
|
|
30
|
+
`<feaas-component library="library" component="test" src="library/test" data="{"data":"1"}">{"data": "1"}</feaas-component>`
|
|
31
|
+
)
|
|
32
|
+
})
|
|
33
|
+
})
|