@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.
Files changed (113) hide show
  1. package/README.md +1 -1
  2. package/build/components/FEAASComponent.js +11 -5
  3. package/build/components/FEAASComponent.js.map +1 -1
  4. package/build/components/FEAASElement.js +34 -9
  5. package/build/components/FEAASElement.js.map +1 -1
  6. package/build/dom/rendering.js +1 -1
  7. package/build/dom/rendering.js.map +1 -1
  8. package/build/dom/thumbnail.js +29 -24
  9. package/build/dom/thumbnail.js.map +1 -1
  10. package/build/headless.js +5 -1
  11. package/build/headless.js.map +1 -1
  12. package/build/ui/FEAASExternal.js +15 -13
  13. package/build/ui/FEAASExternal.js.map +1 -1
  14. package/build/ui/FEAASPicker.js +20 -5
  15. package/build/ui/FEAASPicker.js.map +1 -1
  16. package/build/ui/react.js +33 -10
  17. package/build/ui/react.js.map +1 -1
  18. package/build/{dom → utils}/data.js +3 -0
  19. package/build/utils/data.js.map +1 -0
  20. package/build/utils/datapath.js +21 -2
  21. package/build/utils/datapath.js.map +1 -1
  22. package/build/utils/settings.js +162 -0
  23. package/build/utils/settings.js.map +1 -0
  24. package/dist/browser/headless.cjs +8 -6
  25. package/dist/browser/headless.cjs.map +4 -4
  26. package/dist/browser/headless.esm.js +8 -6
  27. package/dist/browser/headless.esm.js.map +1 -1
  28. package/dist/browser/index.cjs +8 -6
  29. package/dist/browser/index.cjs.map +4 -4
  30. package/dist/browser/index.esm.js +8 -6
  31. package/dist/browser/index.esm.js.map +1 -1
  32. package/dist/browser/react.cjs +8 -6
  33. package/dist/browser/react.cjs.map +4 -4
  34. package/dist/browser/react.esm.js +8 -6
  35. package/dist/browser/react.esm.js.map +1 -1
  36. package/dist/node/headless.cjs +6 -4
  37. package/dist/node/headless.cjs.map +4 -4
  38. package/dist/node/headless.esm.js +6 -4
  39. package/dist/node/headless.esm.js.map +4 -4
  40. package/dist/node/index.cjs +6 -4
  41. package/dist/node/index.cjs.map +4 -4
  42. package/dist/node/index.esm.js +6 -4
  43. package/dist/node/index.esm.js.map +4 -4
  44. package/dist/node/react.cjs +6 -4
  45. package/dist/node/react.cjs.map +4 -4
  46. package/dist/node/react.esm.js +6 -4
  47. package/dist/node/react.esm.js.map +4 -4
  48. package/headless.cjs +6 -4
  49. package/headless.cjs.map +4 -4
  50. package/headless.js +6 -4
  51. package/headless.js.map +4 -4
  52. package/package.json +1 -1
  53. package/react.cjs +8 -6
  54. package/react.cjs.map +4 -4
  55. package/react.js +8 -6
  56. package/react.js.map +4 -4
  57. package/src/components/FEAASComponent.tsx +21 -8
  58. package/src/components/FEAASElement.tsx +35 -8
  59. package/src/dom/component.ts +2 -2
  60. package/src/dom/html.ts +2 -2
  61. package/src/dom/rendering.ts +10 -7
  62. package/src/dom/thumbnail.ts +29 -24
  63. package/src/headless.ts +6 -1
  64. package/src/ui/FEAASExternal.tsx +15 -13
  65. package/src/ui/FEAASPicker.tsx +23 -6
  66. package/src/ui/react.tsx +53 -16
  67. package/src/{dom → utils}/data.ts +3 -1
  68. package/src/utils/datapath.ts +24 -2
  69. package/src/utils/settings.ts +200 -0
  70. package/test/FEAASComponent.react.test.tsx +1 -1
  71. package/test/{FEAASComponent2.test.tsx → FEAASComponentData.test.tsx} +56 -52
  72. package/test/ServerComponent.test.tsx +33 -0
  73. package/test/cdn.ts +1 -1
  74. package/test/datasettings.test.ts +248 -0
  75. package/test/{extras.ts → extras.test.ts} +1 -1
  76. package/test/fetch.ts +2 -2
  77. package/types/components/FEAASComponent.d.ts +10 -5
  78. package/types/components/FEAASComponent.d.ts.map +1 -1
  79. package/types/components/FEAASElement.d.ts +2 -0
  80. package/types/components/FEAASElement.d.ts.map +1 -1
  81. package/types/dom/component.d.ts +2 -2
  82. package/types/dom/component.d.ts.map +1 -1
  83. package/types/dom/html.d.ts +2 -2
  84. package/types/dom/html.d.ts.map +1 -1
  85. package/types/dom/rendering.d.ts +8 -7
  86. package/types/dom/rendering.d.ts.map +1 -1
  87. package/types/dom/thumbnail.d.ts +16 -16
  88. package/types/dom/thumbnail.d.ts.map +1 -1
  89. package/types/headless.d.ts +6 -1
  90. package/types/headless.d.ts.map +1 -1
  91. package/types/tsbuildinfo +1 -1
  92. package/types/ui/FEAASExternal.d.ts +15 -13
  93. package/types/ui/FEAASExternal.d.ts.map +1 -1
  94. package/types/ui/FEAASPicker.d.ts +9 -6
  95. package/types/ui/FEAASPicker.d.ts.map +1 -1
  96. package/types/ui/react.d.ts +8 -3
  97. package/types/ui/react.d.ts.map +1 -1
  98. package/types/ui/tsbuildinfo +1 -1
  99. package/types/{dom → utils}/data.d.ts +1 -1
  100. package/types/utils/data.d.ts.map +1 -0
  101. package/types/utils/datapath.d.ts +3 -0
  102. package/types/utils/datapath.d.ts.map +1 -1
  103. package/types/utils/settings.d.ts +89 -0
  104. package/types/utils/settings.d.ts.map +1 -0
  105. package/build/dom/data.js.map +0 -1
  106. package/build/utils/fetch.js +0 -26
  107. package/build/utils/fetch.js.map +0 -1
  108. package/src/utils/fetch.ts +0 -44
  109. package/test/cjs/tsconfig.json +0 -9
  110. package/test/fetch-data.ts +0 -55
  111. package/types/dom/data.d.ts.map +0 -1
  112. package/types/utils/fetch.d.ts +0 -9
  113. 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 { fetchData } from '../utils/fetch.js'
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: any) {
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 fetchData(this.props.data)
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
- /** @deprecated Use cdn instead */
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.dispatchEvent(new Event('feaasMount', { bubbles: true, composed: true }))
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.dispatchEvent(new Event('feaasUnmount', { bubbles: true, composed: true }))
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, -1)
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.dispatchEvent(event)
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.dispatchEvent(new Event('feaasLoad', { bubbles: true, composed: true }))
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
- if (value == null || value === this.defaultProps[property as keyof typeof this.defaultProps]) {
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 (this.getAttribute(attribute) != String(value)) this.setAttribute(attribute, String(value))
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) {
@@ -1,8 +1,8 @@
1
1
  import { fetchAndRevalidateComponent } from '../cdn.js'
2
- import { Datascopes } from './data.js'
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?: Datascopes) {
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 { Datascopes } from './data.js'
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?: Datascopes, config?: FEAASCustomizations) {
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
@@ -1,22 +1,24 @@
1
1
  import { FEAASCDNComponentProps } from '../cdn.js'
2
- import { Datascopes, queryScopes, serializeCH1RichText } from './data.js'
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: any) => string
9
- processData?: (element: HTMLElement, data: any) => any
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: any) => void
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 = Datascopes> = Partial<ExplicitTemplate> & {
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?: Datascopes, config?: FEAASCustomizations) {
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?: Datascopes, config?: FEAASCustomizations, fakeDOM = false) {
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)
@@ -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 - The component for which the version needs to be obtained.
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
- * pixels.
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 - 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).
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
- * cached image immediately while the updated image is being downloaded in the background.
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 - 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 version
93
- * 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 the
95
- * reasons described above.
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 - Component instance.
147
- * @param {VersionModel} [version] - Version instance.
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[]} 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.
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
- * created image.
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 - Component instance.
258
- * @param {VersionModel} version - Version instance.
259
- * @param {Blob} blob - Blob instance to upload.
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 './dom/data.js'
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 {
@@ -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 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.
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)
@@ -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' | 'datasources',
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
- onContextAttributeSet: undefined as (context: any, attribute: string, value: string) => void
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
- mode={mode || undefined}
52
- onModeChange={(mode) => {
53
- if (!mode) this.props.onClose(mode)
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 { DatasourceModel, SDK } from '@sitecore-feaas/sdk'
2
-
3
- export * from './index.js'
4
- import { FEAASComponentProps, parseComponentSource } from './index.js'
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
- var html = renderHTMLContent(props.template, data || {})
37
+ html = renderHTMLContent(props.template, data || {})
14
38
  }
15
39
 
16
40
  if ('src' in props) {
17
- props = {
18
- ...parseComponentSource(props.src),
19
- ...props
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
- <feaas-component
25
- {...props}
26
- template={null}
27
- data={typeof data == 'string' ? data : data ? JSON.stringify(data) : null}
28
- dangerouslySetInnerHTML={{ __html: html || '' }}
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 = FEAASEmbed
70
+ export const Component = FEAASReactComponent
@@ -1,6 +1,8 @@
1
1
  import { JSONPath } from 'jsonpath-plus'
2
2
 
3
- export type Datascopes = Record<string, any> | any[]
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
@@ -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(/(\.\*|\[[^\]]+\])$/)?.[0]
23
+ export const getQualifier = (jsonpath: string) => jsonpath?.match(/(\.\*|\[[^\]]*\])$/)?.[0]
24
24
 
25
25
  export function cleanCollectionBit(path: string) {
26
- return path.replace(/(\[[^\]\[]+\]|\.\*)$/g, '')
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
+ }