@sitecore-feaas/clientside 0.2.1 → 0.2.2

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