@sitecore-feaas/clientside 0.1.28 → 0.1.29

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 (156) hide show
  1. package/build/cdn.js +115 -0
  2. package/build/cdn.js.map +1 -0
  3. package/build/components/FEAASBase.js +212 -79
  4. package/build/components/FEAASBase.js.map +1 -1
  5. package/build/components/FEAASComponent.js +50 -17
  6. package/build/components/FEAASComponent.js.map +1 -1
  7. package/build/components/FEAASLoader.js +6 -0
  8. package/build/components/FEAASLoader.js.map +1 -1
  9. package/build/components/FEAASStylesheet.js +18 -13
  10. package/build/components/FEAASStylesheet.js.map +1 -1
  11. package/build/dom/component.js +34 -0
  12. package/build/dom/component.js.map +1 -0
  13. package/build/dom/data.js +105 -0
  14. package/build/dom/data.js.map +1 -0
  15. package/build/{dom.js → dom/rendering.js} +3 -194
  16. package/build/dom/rendering.js.map +1 -0
  17. package/build/dom/scripts.js +48 -0
  18. package/build/dom/scripts.js.map +1 -0
  19. package/build/dom/stylesheet.js +39 -0
  20. package/build/dom/stylesheet.js.map +1 -0
  21. package/build/fetch.js +72 -22
  22. package/build/fetch.js.map +1 -1
  23. package/build/{basic.js → headless.js} +11 -3
  24. package/build/headless.js.map +1 -0
  25. package/build/ui/FEAASContext.js +10 -3
  26. package/build/ui/FEAASContext.js.map +1 -1
  27. package/build/ui/FEAASEditor.js +5 -0
  28. package/build/ui/FEAASEditor.js.map +1 -1
  29. package/build/ui/FEAASPicker.js +7 -1
  30. package/build/ui/FEAASPicker.js.map +1 -1
  31. package/build/ui/index.js +1 -1
  32. package/build/ui/index.js.map +1 -1
  33. package/build/ui/react.js +6 -0
  34. package/build/ui/react.js.map +1 -0
  35. package/build/utils/promise.js +26 -0
  36. package/build/utils/promise.js.map +1 -0
  37. package/dist/browser/headless.esm.js +69 -0
  38. package/dist/browser/headless.esm.js.map +1 -0
  39. package/dist/browser/headless.js +69 -0
  40. package/dist/browser/headless.js.map +1 -0
  41. package/dist/browser/index.esm.js +3 -5
  42. package/dist/browser/index.esm.js.map +1 -1
  43. package/dist/browser/index.js +5 -5
  44. package/dist/browser/index.js.map +1 -1
  45. package/dist/browser/react.js +67 -0
  46. package/dist/browser/react.js.map +1 -0
  47. package/dist/node/headless.esm.js +67 -0
  48. package/dist/node/headless.esm.js.map +7 -0
  49. package/dist/node/headless.js +67 -0
  50. package/dist/node/headless.js.map +7 -0
  51. package/dist/node/index.esm.js +3 -3
  52. package/dist/node/index.esm.js.map +4 -4
  53. package/dist/node/index.js +3 -3
  54. package/dist/node/index.js.map +4 -4
  55. package/dist/node/react.js +67 -0
  56. package/dist/node/react.js.map +7 -0
  57. package/examples/index.html +0 -5
  58. package/package.json +23 -12
  59. package/scripts/build.js +17 -6
  60. package/scripts/test-setup.ts +1 -0
  61. package/src/cdn.ts +188 -0
  62. package/src/components/FEAASBase.tsx +234 -82
  63. package/src/components/FEAASComponent.tsx +56 -21
  64. package/src/components/FEAASLoader.tsx +4 -0
  65. package/src/components/FEAASStylesheet.tsx +14 -13
  66. package/src/dom/component.ts +35 -0
  67. package/src/dom/data.ts +124 -0
  68. package/src/{dom.ts → dom/rendering.ts} +4 -218
  69. package/src/dom/scripts.ts +47 -0
  70. package/src/dom/stylesheet.ts +48 -0
  71. package/src/fetch.ts +79 -25
  72. package/src/{basic.ts → headless.ts} +11 -2
  73. package/src/ui/FEAASContext.tsx +12 -4
  74. package/src/ui/FEAASEditor.tsx +5 -0
  75. package/src/ui/FEAASPicker.tsx +6 -2
  76. package/src/ui/index.ts +1 -1
  77. package/src/ui/react.tsx +8 -0
  78. package/src/utils/promise.ts +34 -0
  79. package/test/FEAASBase.test.tsx +195 -0
  80. package/test/FEAASComponent.test.tsx +284 -0
  81. package/test/FEAASContext.test.tsx +76 -0
  82. package/test/FEAASPicker.test.tsx +40 -0
  83. package/test/FEAASStylesheet.test.tsx +159 -0
  84. package/test/cdn.ts +39 -0
  85. package/test/{render.ts → dom.test.ts} +12 -6
  86. package/test/extras.ts +1 -1
  87. package/test/fetch.ts +254 -0
  88. package/test/tsconfig.json +4 -1
  89. package/types/cdn.d.ts +57 -0
  90. package/types/cdn.d.ts.map +1 -0
  91. package/types/components/FEAASBase.d.ts +220 -16
  92. package/types/components/FEAASBase.d.ts.map +1 -1
  93. package/types/components/FEAASComponent.d.ts +28 -7
  94. package/types/components/FEAASComponent.d.ts.map +1 -1
  95. package/types/components/FEAASLoader.d.ts +4 -0
  96. package/types/components/FEAASLoader.d.ts.map +1 -1
  97. package/types/components/FEAASStylesheet.d.ts +5 -13
  98. package/types/components/FEAASStylesheet.d.ts.map +1 -1
  99. package/types/dom/component.d.ts +6 -0
  100. package/types/dom/component.d.ts.map +1 -0
  101. package/types/dom/data.d.ts +15 -0
  102. package/types/dom/data.d.ts.map +1 -0
  103. package/types/dom/rendering.d.ts +20 -0
  104. package/types/dom/rendering.d.ts.map +1 -0
  105. package/types/dom/scripts.d.ts +4 -0
  106. package/types/dom/scripts.d.ts.map +1 -0
  107. package/types/dom/stylesheet.d.ts +8 -0
  108. package/types/dom/stylesheet.d.ts.map +1 -0
  109. package/types/fetch.d.ts +25 -11
  110. package/types/fetch.d.ts.map +1 -1
  111. package/types/headless.d.ts +24 -0
  112. package/types/headless.d.ts.map +1 -0
  113. package/types/tsbuildinfo +1 -1
  114. package/types/tsbuildinfo_ui +1 -1
  115. package/types/ui/FEAASContext.d.ts +15 -5
  116. package/types/ui/FEAASContext.d.ts.map +1 -1
  117. package/types/ui/FEAASEditor.d.ts +18 -0
  118. package/types/ui/FEAASEditor.d.ts.map +1 -1
  119. package/types/ui/FEAASPicker.d.ts +15 -3
  120. package/types/ui/FEAASPicker.d.ts.map +1 -1
  121. package/types/ui/index.d.ts +1 -1
  122. package/types/ui/index.d.ts.map +1 -1
  123. package/types/ui/react.d.ts +7 -0
  124. package/types/ui/react.d.ts.map +1 -0
  125. package/types/utils/promise.d.ts +8 -0
  126. package/types/utils/promise.d.ts.map +1 -0
  127. package/build/basic.js.map +0 -1
  128. package/build/cloud.js +0 -71
  129. package/build/cloud.js.map +0 -1
  130. package/build/dom.js.map +0 -1
  131. package/build/node.js +0 -10
  132. package/build/node.js.map +0 -1
  133. package/build/ui/node.js +0 -3
  134. package/build/ui/node.js.map +0 -1
  135. package/dist/browser/basic.esm.js +0 -69
  136. package/dist/browser/basic.esm.js.map +0 -1
  137. package/dist/browser/basic.js +0 -69
  138. package/dist/browser/basic.js.map +0 -1
  139. package/dist/node/basic.esm.js +0 -67
  140. package/dist/node/basic.esm.js.map +0 -7
  141. package/dist/node/basic.js +0 -67
  142. package/dist/node/basic.js.map +0 -7
  143. package/src/cloud.ts +0 -119
  144. package/src/node.ts +0 -10
  145. package/src/ui/node.ts +0 -2
  146. package/test/components.ts +0 -31
  147. package/types/basic.d.ts +0 -16
  148. package/types/basic.d.ts.map +0 -1
  149. package/types/cloud.d.ts +0 -39
  150. package/types/cloud.d.ts.map +0 -1
  151. package/types/dom.d.ts +0 -41
  152. package/types/dom.d.ts.map +0 -1
  153. package/types/node.d.ts +0 -4
  154. package/types/node.d.ts.map +0 -1
  155. package/types/ui/node.d.ts +0 -3
  156. package/types/ui/node.d.ts.map +0 -1
@@ -1,29 +1,34 @@
1
- import { fetchAndRevalidateComponent } from '../cloud.js'
2
- import { renderDOMContent, renderDOMElement } from '../dom.js'
3
- import { FEAASBase, OptionalExcept } from './FEAASBase.js'
4
- import { FEAASLoader } from './FEAASLoader.js'
1
+ import { DEFAULT_CDN_URL, FEAASCDNComponentParams, fetchAndRevalidateComponent, parseComponentSource } from '../cdn.js'
2
+ import { renderDOMContent, renderDOMElement } from '../dom/rendering.js'
3
+ import { loadStylesheetAllowStale } from '../headless.js'
4
+ import { FEAASBase } from './FEAASBase.js'
5
5
 
6
6
  export function FEAASComponentsProps(element: FEAASBase = this) {
7
7
  let data: any
8
+ const src = element.getAttribute('src')
9
+ if (src) {
10
+ var { cdn, library, component, version, revision } = parseComponentSource(src)
11
+ }
8
12
  const dataValue = element.getAttribute('data')
9
- if (dataValue != null) {
13
+ if (dataValue != null && dataValue != '') {
10
14
  try {
11
15
  data = typeof dataValue == 'string' ? JSON.parse(dataValue) : {}
12
16
  } catch (e) {}
13
17
  }
14
18
  return {
15
- data: data ?? {},
16
- library: element.getAttribute('library'),
17
- component: element.getAttribute('component'),
18
- version: element.getAttribute('version'),
19
- revision: element.getAttribute('revision'),
19
+ data: data || {},
20
+ cdn: element.getContextAttribute('cdn') ?? cdn ?? DEFAULT_CDN_URL,
21
+ library: element.getAttribute('library') ?? library,
22
+ component: element.getAttribute('component') ?? component,
23
+ version: element.getAttribute('version') ?? version ?? 'responsive',
24
+ revision: (element.getAttribute('revision') ?? revision ?? 'published') as FEAASCDNComponentParams['revision'],
20
25
  hostname: element.getAttribute('hostname'),
21
- cdn: element.getContextAttribute('cdn'),
22
26
  template: element.getAttribute('template'),
23
27
  instance: element.getAttribute('instance'),
24
28
  editable: element.getAttribute('editable') != null,
25
29
  suspended: element.getAttribute('suspended') != null,
26
- lastModified: element.getAttribute('last-modified')
30
+ lastModified: element.getAttribute('last-modified'),
31
+ withStylesheet: element.getAttribute('with-stylesheet') != 'false'
27
32
  }
28
33
  }
29
34
  export type FEAASComponentsProps = ReturnType<typeof FEAASComponentsProps>
@@ -38,9 +43,19 @@ export class FEAASComponent extends FEAASBase<FEAASComponentsProps, string> {
38
43
  'template',
39
44
  'data',
40
45
  'instance',
41
- 'editable'
46
+ 'editable',
47
+ 'with-stylesheet'
42
48
  ]
43
49
 
50
+ defaultProps = {
51
+ cdn: DEFAULT_CDN_URL,
52
+ revision: 'published',
53
+ version: 'responsive',
54
+ withStylesheet: true,
55
+ editable: false,
56
+ suspended: false
57
+ } as Partial<FEAASComponentsProps>
58
+
44
59
  needsRefresh: boolean
45
60
  setData(data: any) {
46
61
  this.set({ data })
@@ -52,7 +67,10 @@ export class FEAASComponent extends FEAASBase<FEAASComponentsProps, string> {
52
67
 
53
68
  attributeChangedCallback(name: string, oldValue: string, newValue: string): void {
54
69
  if (oldValue === newValue) return
55
- this.payload = undefined
70
+ // force component to be refreshed
71
+ if (name == 'library' || name == 'component' || name == 'revision' || name == 'version') {
72
+ this.payload = undefined
73
+ }
56
74
  this.needsRefresh = true
57
75
  super.attributeChangedCallback(name, oldValue, newValue)
58
76
  }
@@ -98,18 +116,31 @@ export class FEAASComponent extends FEAASBase<FEAASComponentsProps, string> {
98
116
  )
99
117
  }
100
118
 
119
+ onLoad = (html: string) => {
120
+ this.payload = html
121
+ this.advance()
122
+ }
123
+
101
124
  async load() {
102
125
  // defer loading to original element
103
126
  if (this.getOriginal()) {
104
127
  return
105
128
  }
106
- try {
107
- fetchAndRevalidateComponent(this.props, this.onLoad)
108
- } catch {
109
- console.error(
110
- `FEAAS: Could not fetch component: ${this.props.component}/${this.props.version}/${this.props.revision}`
111
- )
129
+ if (this.props.withStylesheet) {
130
+ var stylesheetPromise = loadStylesheetAllowStale(this.props).then(() => {
131
+ stylesheetPromise = null
132
+ if (this.payload) {
133
+ this.onLoad(this.payload)
134
+ }
135
+ })
112
136
  }
137
+ return Promise.all([
138
+ stylesheetPromise,
139
+ fetchAndRevalidateComponent(this.props, (html) => {
140
+ this.payload = html
141
+ if (!stylesheetPromise) this.onLoad(html)
142
+ })
143
+ ]).then(() => this.payload)
113
144
  }
114
145
 
115
146
  update() {
@@ -129,10 +160,14 @@ export class FEAASComponent extends FEAASBase<FEAASComponentsProps, string> {
129
160
  declare global {
130
161
  namespace JSX {
131
162
  interface IntrinsicElements {
132
- 'feaas-component': OptionalExcept<FEAASComponentsProps, 'component' | 'version' | 'revision'> & {
163
+ 'feaas-component': (Partial<
164
+ Omit<FEAASComponentsProps, 'component' | 'version' | 'revision' | 'lastModified' | 'withStylesheet'>
165
+ > &
166
+ (Pick<FEAASComponentsProps, 'component' | 'version' | 'revision'> | { src: string })) & {
133
167
  children?: any
134
168
  dangerouslySetInnerHTML?: { __html: string }
135
169
  'last-modified'?: string
170
+ 'with-stylesheet'?: 'false' | 'true' | ''
136
171
  }
137
172
  }
138
173
  }
@@ -16,6 +16,10 @@ export class FEAASLoader extends FEAASBase<FEAASLoaderProps, string> {
16
16
  return FEAASLoaderProps(this)
17
17
  }
18
18
 
19
+ defaultProps = {
20
+ hidden: false
21
+ } as Partial<FEAASLoaderProps>
22
+
19
23
  static observedAttributes = ['hidden', 'opacity']
20
24
 
21
25
  attributeChangedCallback(name: string, oldValue: string, newValue: string): void {
@@ -1,17 +1,21 @@
1
- import { fetchStylesheet } from '../cloud.js'
1
+ import { DEFAULT_CDN_URL, FEAASCDNStylesheetParams, parseStylesheetSource } from '../cdn.js'
2
+ import { loadStylesheet } from '../dom/stylesheet.js'
2
3
  import { FEAASBase } from './FEAASBase.js'
3
4
 
4
5
  export function FEAASStylesheetProps(element: FEAASBase = this) {
6
+ const src = element.getAttribute('src')
7
+ if (src) {
8
+ var { cdn, library, revision } = parseStylesheetSource(src)
9
+ }
5
10
  return {
6
11
  library: element?.getContextAttribute('library'),
7
- revision: (element?.getAttribute('revision') ?? 'published') as 'published' | 'staging' | 'saved' | number,
8
- cdn: element?.getContextAttribute('cdn') || element?.getContextAttribute('hostname')
9
- }
12
+ revision: element?.getAttribute('revision') ?? revision ?? 'published',
13
+ cdn: element?.getContextAttribute('cdn') ?? element?.getContextAttribute('hostname') ?? DEFAULT_CDN_URL
14
+ } as FEAASCDNStylesheetParams
10
15
  }
11
16
  export type FEAASStylesheetProps = ReturnType<typeof FEAASStylesheetProps>
12
17
 
13
18
  export class FEAASStylesheet extends FEAASBase<FEAASStylesheetProps, string> {
14
- stylesheet: HTMLStyleElement
15
19
  getProps() {
16
20
  return FEAASStylesheetProps(this)
17
21
  }
@@ -20,16 +24,13 @@ export class FEAASStylesheet extends FEAASBase<FEAASStylesheetProps, string> {
20
24
  return this.props.library != null && this.props.revision != null && this.props.cdn != null
21
25
  }
22
26
 
23
- async load() {
24
- fetchStylesheet(this.props, this.onLoad)
27
+ onLoad = (html: string) => {
28
+ this.payload = html
29
+ this.advance()
25
30
  }
26
31
 
27
- update() {
28
- if (!this.stylesheet) {
29
- this.stylesheet = document.createElement('style')
30
- this.appendChild(this.stylesheet)
31
- }
32
- this.stylesheet.textContent = this.payload
32
+ load() {
33
+ return loadStylesheet(this.props, this.onLoad)
33
34
  }
34
35
  }
35
36
 
@@ -0,0 +1,35 @@
1
+ import { fetchAndRevalidateComponent } from '../cdn.js'
2
+ import { Datascopes } from './data.js'
3
+ import { FEAASComponentProps, renderDOMContent, renderDOMElement } from './rendering.js'
4
+
5
+ export function renderComponentElement(content: string, datasources?: Datascopes) {
6
+ const wrapper = document.createElement('div')
7
+ wrapper.innerHTML = content
8
+ const fragment = document.createDocumentFragment()
9
+ while (wrapper.firstChild) {
10
+ fragment.appendChild(wrapper.firstChild)
11
+ }
12
+ renderDOMElement(fragment, datasources)
13
+ return wrapper
14
+ }
15
+
16
+ export function renderComponent(props: FEAASComponentProps, root?: HTMLElement) {
17
+ renderComponentPromise(props, (root ||= document.createElement('div')))
18
+ return root
19
+ }
20
+
21
+ export async function renderComponentPromise(props: FEAASComponentProps, root?: HTMLElement) {
22
+ root ||= document.createElement('div')
23
+ if (!root.childNodes.length) {
24
+ if ('template' in props) {
25
+ return renderDOMContent(root, props.template, props.data, props)
26
+ } else {
27
+ return fetchAndRevalidateComponent(props, (template) => {
28
+ return renderDOMContent(root, template, props.data, props)
29
+ }).then(() => root)
30
+ }
31
+ /* update element with new data */
32
+ } else {
33
+ return renderDOMElement(root, props.data, props)
34
+ }
35
+ }
@@ -0,0 +1,124 @@
1
+ import { JSONPath } from 'jsonpath-plus'
2
+
3
+ export type Datascopes = Record<string, any>
4
+
5
+ export function cleanCollectionBit(path: string) {
6
+ return path.replace(/(\[[^\]\[]+\]|\.\*)$/g, '')
7
+ }
8
+
9
+ export function normalizeCollectionScope(path: string) {
10
+ return cleanCollectionBit(path) + '.*'
11
+ }
12
+
13
+ export function queryScopes(scopes: any, datapath: string, singleValue = false, json: any = scopes) {
14
+ var path = datapath
15
+ /* find closest parent scope */
16
+ const keys = Object.keys(scopes).sort((b, a) => a.length - b.length)
17
+ for (const scope of keys) {
18
+ if (datapath.startsWith(scope) && 'ends with it') {
19
+ if (scope == datapath) {
20
+ if (singleValue) {
21
+ return scopes[scope]
22
+ } else {
23
+ continue
24
+ }
25
+ }
26
+ path = datapath.substring(scope.length + 1)
27
+ json = scopes[scope]
28
+ break
29
+ }
30
+ }
31
+
32
+ var collection = JSONPath({ path, json })
33
+
34
+ // If regular path didn't match any data, try fallbacks
35
+ // datasourceId.wrapper.$.path
36
+ if (Array.isArray(collection) && collection?.length == 0) {
37
+ collection = [
38
+ // look up in `_` datasource id (catch-all id is used in JSS)
39
+ // _.wrapper.$.path
40
+ path.replace(/^.*?\./, '_.'),
41
+ // look up directly on data root
42
+ // wrapper.$.path
43
+ path.replace(/^.*?\./, ''),
44
+ // look up trimmed path in datasource
45
+ // datasourceId.path
46
+ path.replace(/^([^.]+).*?\.\$\./, '$1.'),
47
+ // look up trimmed path in catch all datasource
48
+ // _.path
49
+ path.replace(/^.*?\.\$\./, '_.'),
50
+ // look up trimmed path on root
51
+ // path
52
+ path.replace(/^.*?\.\$\./, ''),
53
+ // for case of top-level object data mapping full path can be replaced to _
54
+ path.replace(/^[^.]+$/, '_')
55
+ ]
56
+ .filter((v, i, a) => a.indexOf(v) === i)
57
+ .reduce((v, path) => {
58
+ return v.length > 0 ? v : JSONPath({ path, json })
59
+ }, [] as any[])
60
+ }
61
+
62
+ if (singleValue && Array.isArray(collection)) {
63
+ return collection[0]
64
+ } else {
65
+ return collection
66
+ }
67
+ }
68
+
69
+ export type CH1Node =
70
+ | {
71
+ type: string
72
+ text?: string
73
+ content?: CH1Node[]
74
+ marks?: CH1Node[]
75
+ attrs?: Record<string, any>
76
+ }
77
+ | CH1Node[]
78
+ function serializeCH1Tag(data: any, attrs: Record<string, string> = {}, tag: string): string {
79
+ if (!tag) return serializeCH1RichText(data)
80
+ return `<${tag}${Object.entries(attrs)
81
+ .map(([k, v]) => v != null && ` ${k}="${v}"`)
82
+ .filter(Boolean)
83
+ .join('')}>${serializeCH1RichText(data)}</${tag}>`.replace(/^<([a-z0-9][^>]*)><\/[^>]+>$/, '<$1 />')
84
+ }
85
+ export function serializeCH1RichText(data: CH1Node | string | null | undefined): string {
86
+ if (Array.isArray(data)) return data.map(serializeCH1RichText).join('\n').trim()
87
+ if (typeof data == 'string') return data
88
+ if (data == null) return ''
89
+ switch (data.type) {
90
+ case 'heading':
91
+ const { level, ...attrs } = data.attrs
92
+ return serializeCH1Tag(data.content, attrs, 'h' + level)
93
+ case 'text':
94
+ return ((data.marks || []) as any[]).reduce((text, mark) => {
95
+ return serializeCH1Tag(
96
+ text,
97
+ mark.attrs,
98
+ {
99
+ link: 'a',
100
+ bold: 'strong',
101
+ italic: 'em',
102
+ strike: 's',
103
+ underline: 'u'
104
+ }[mark.type as string]
105
+ )
106
+ }, data.text?.replace(/\s+/, ' '))
107
+ default:
108
+ return serializeCH1Tag(
109
+ data.content || data.text,
110
+ data.attrs,
111
+ {
112
+ bulletList: 'ul',
113
+ orderedList: 'ol',
114
+ listItem: 'li',
115
+ horizontalRule: 'hr',
116
+ paragraph: 'p',
117
+ codeBlock: 'code',
118
+ blockquote: 'blockquote'
119
+ }[data.type]
120
+ )
121
+ }
122
+ }
123
+
124
+ export { JSONPath }
@@ -1,5 +1,6 @@
1
- import { JSONPath } from 'jsonpath-plus'
2
- import { CloudStylesheet, CloudTemplate, fetchAndRevalidateComponent, fetchStylesheet } from './cloud.js'
1
+ import { FEAASCDNComponentProps } from '../cdn.js'
2
+ import { Datascopes, normalizeCollectionScope, queryScopes, serializeCH1RichText } from './data.js'
3
+ import { loadScript } from './scripts.js'
3
4
 
4
5
  export interface FEAASCustomizations {
5
6
  processTemplate?: (element: HTMLElement, template: string, data: any) => string
@@ -13,32 +14,11 @@ export interface ExplicitTemplate {
13
14
  template: string
14
15
  }
15
16
 
16
- export type Datascopes = Record<string, any>
17
-
18
17
  export type FEAASComponentProps = {
19
18
  data?: Datascopes
20
- } & (ExplicitTemplate | CloudTemplate) &
19
+ } & (ExplicitTemplate | FEAASCDNComponentProps) &
21
20
  FEAASCustomizations
22
21
 
23
- export function renderComponentElement(content: string, datasources?: Datascopes) {
24
- const wrapper = document.createElement('div')
25
- wrapper.innerHTML = content
26
- const fragment = document.createDocumentFragment()
27
- while (wrapper.firstChild) {
28
- fragment.appendChild(wrapper.firstChild)
29
- }
30
- renderDOMElement(fragment, datasources)
31
- return wrapper
32
- }
33
-
34
- export function cleanCollectionBit(path: string) {
35
- return path.replace(/(\[[^\]\[]+\]|\.\*)$/g, '')
36
- }
37
-
38
- export function normalizeCollectionScope(path: string) {
39
- return cleanCollectionBit(path) + '.*'
40
- }
41
-
42
22
  function getDOMCollectionIndex(element: HTMLElement) {
43
23
  const datascope = element.getAttribute('data-path-scope')
44
24
  var index = 0
@@ -91,62 +71,6 @@ function updateDOMClones(element: HTMLElement, targetCount: number, config?: FEA
91
71
  }
92
72
  }
93
73
 
94
- export function queryScopes(scopes: any, datapath: string, singleValue = false, json: any = scopes) {
95
- var path = datapath
96
- /* find closest parent scope */
97
- const keys = Object.keys(scopes).sort((b, a) => a.length - b.length)
98
- for (const scope of keys) {
99
- if (datapath.startsWith(scope) && 'ends with it') {
100
- if (scope == datapath) {
101
- if (singleValue) {
102
- return scopes[scope]
103
- } else {
104
- continue
105
- }
106
- }
107
- path = datapath.substring(scope.length + 1)
108
- json = scopes[scope]
109
- break
110
- }
111
- }
112
-
113
- var collection = JSONPath({ path, json })
114
-
115
- // If regular path didn't match any data, try fallbacks
116
- // datasourceId.wrapper.$.path
117
- if (Array.isArray(collection) && collection?.length == 0) {
118
- collection = [
119
- // look up in `_` datasource id (catch-all id is used in JSS)
120
- // _.wrapper.$.path
121
- path.replace(/^.*?\./, '_.'),
122
- // look up directly on data root
123
- // wrapper.$.path
124
- path.replace(/^.*?\./, ''),
125
- // look up trimmed path in datasource
126
- // datasourceId.path
127
- path.replace(/^([^.]+).*?\.\$\./, '$1.'),
128
- // look up trimmed path in catch all datasource
129
- // _.path
130
- path.replace(/^.*?\.\$\./, '_.'),
131
- // look up trimmed path on root
132
- // path
133
- path.replace(/^.*?\.\$\./, ''),
134
- // for case of top-level object data mapping full path can be replaced to _
135
- path.replace(/^[^.]+$/, '_')
136
- ]
137
- .filter((v, i, a) => a.indexOf(v) === i)
138
- .reduce((v, path) => {
139
- return v.length > 0 ? v : JSONPath({ path, json })
140
- }, [] as any[])
141
- }
142
-
143
- if (singleValue && Array.isArray(collection)) {
144
- return collection[0]
145
- } else {
146
- return collection
147
- }
148
- }
149
-
150
74
  export function setDOMAttribute(element: HTMLElement, attribute: string, value: any, config?: FEAASCustomizations) {
151
75
  if (!element) return
152
76
  var target = element
@@ -213,54 +137,6 @@ export function setDOMAttribute(element: HTMLElement, attribute: string, value:
213
137
  }
214
138
  }
215
139
 
216
- export function observeDOMElement(element: HTMLElement) {
217
- const observer = new element.ownerDocument.defaultView.MutationObserver((changes) => {
218
- changes.forEach((change) => {
219
- const target = change.target as HTMLElement
220
- const src = target.getAttribute('data-embed-src')
221
- if (src) {
222
- loadScript(target, src)
223
- }
224
-
225
- if (change.addedNodes) {
226
- change.addedNodes.forEach((node) => {
227
- if (node.nodeType == 1) autoloadScripts(node as HTMLElement, false)
228
- })
229
- }
230
- })
231
- })
232
- observer.observe(element, { attributes: true, subtree: true, childList: true })
233
- return observer
234
- }
235
-
236
- export function autoloadScripts(element: HTMLElement, observe = true) {
237
- const embeds = element.querySelectorAll('[data-embed-src]')
238
- for (var i = 0; i < embeds.length; i++) {
239
- const embed = embeds[i]
240
- loadScript(embed as HTMLElement, embed.getAttribute('data-embed-src'))
241
- }
242
- if (observe) return observeDOMElement(element)
243
- }
244
-
245
- export function loadScript(element: HTMLElement, src: string) {
246
- for (var p = element; p; p = p.parentElement) {
247
- if (!p.parentElement) {
248
- const scripts = p.querySelectorAll('script')
249
- for (var i = 0; i < scripts.length; i++) {
250
- if (scripts[i].getAttribute('src') == src) {
251
- return true
252
- }
253
- }
254
- if (p.tagName == 'HTML') p = p.querySelector('head') || p
255
- const script = p.ownerDocument.createElement('script')
256
- script.type = 'module'
257
- script.src = src
258
- p.appendChild(script)
259
- break
260
- }
261
- }
262
- }
263
-
264
140
  // order in which attributes are proccesed. Rightmost are more important
265
141
  var sortOrder = ['data-attributes-keys', 'data-path-attributes', 'data-path-scope']
266
142
  export function renderDOMElement(
@@ -343,93 +219,3 @@ export function renderHTMLContent(template: string, data?: Datascopes, config?:
343
219
  return root.innerHTML
344
220
  }
345
221
  }
346
-
347
- export function renderStylesheet(props: CloudStylesheet, style?: HTMLStyleElement) {
348
- renderStylesheetPromise(props, (style ||= document.createElement('style')))
349
- return style
350
- }
351
-
352
- export function renderStylesheetPromise(props: CloudStylesheet, style?: HTMLStyleElement) {
353
- style ||= document.createElement('style')
354
- return fetchStylesheet(props, (cssText) => {
355
- style.textContent = cssText
356
- }).then(() => style)
357
- }
358
-
359
- export function renderComponent(props: FEAASComponentProps, root?: HTMLElement) {
360
- renderComponentPromise(props, (root ||= document.createElement('div')))
361
- return root
362
- }
363
-
364
- export async function renderComponentPromise(props: FEAASComponentProps, root?: HTMLElement) {
365
- root ||= document.createElement('div')
366
- if (!root.childNodes.length) {
367
- if ('template' in props) {
368
- return renderDOMContent(root, props.template, props.data, props)
369
- } else {
370
- return fetchAndRevalidateComponent(props, (template) => {
371
- return renderDOMContent(root, template, props.data, props)
372
- }).then(() => root)
373
- }
374
- /* update element with new data */
375
- } else {
376
- return renderDOMElement(root, props.data, props)
377
- }
378
- }
379
-
380
- export type CH1Node =
381
- | {
382
- type: string
383
- text?: string
384
- content?: CH1Node[]
385
- marks?: CH1Node[]
386
- attrs?: Record<string, any>
387
- }
388
- | CH1Node[]
389
- function serializeCH1Tag(data: any, attrs: Record<string, string> = {}, tag: string): string {
390
- if (!tag) return serializeCH1RichText(data)
391
- return `<${tag}${Object.entries(attrs)
392
- .map(([k, v]) => v != null && ` ${k}="${v}"`)
393
- .filter(Boolean)
394
- .join('')}>${serializeCH1RichText(data)}</${tag}>`.replace(/^<([a-z0-9][^>]*)><\/[^>]+>$/, '<$1 />')
395
- }
396
- export function serializeCH1RichText(data: CH1Node | string | null | undefined): string {
397
- if (Array.isArray(data)) return data.map(serializeCH1RichText).join('\n').trim()
398
- if (typeof data == 'string') return data
399
- if (data == null) return ''
400
- switch (data.type) {
401
- case 'heading':
402
- const { level, ...attrs } = data.attrs
403
- return serializeCH1Tag(data.content, attrs, 'h' + level)
404
- case 'text':
405
- return ((data.marks || []) as any[]).reduce((text, mark) => {
406
- return serializeCH1Tag(
407
- text,
408
- mark.attrs,
409
- {
410
- link: 'a',
411
- bold: 'strong',
412
- italic: 'em',
413
- strike: 's',
414
- underline: 'u'
415
- }[mark.type as string]
416
- )
417
- }, data.text?.replace(/\s+/, ' '))
418
- default:
419
- return serializeCH1Tag(
420
- data.content || data.text,
421
- data.attrs,
422
- {
423
- bulletList: 'ul',
424
- orderedList: 'ol',
425
- listItem: 'li',
426
- horizontalRule: 'hr',
427
- paragraph: 'p',
428
- codeBlock: 'code',
429
- blockquote: 'blockquote'
430
- }[data.type]
431
- )
432
- }
433
- }
434
-
435
- export { JSONPath }
@@ -0,0 +1,47 @@
1
+ export function observeDOMElement(element: HTMLElement) {
2
+ const observer = new element.ownerDocument.defaultView.MutationObserver((changes) => {
3
+ changes.forEach((change) => {
4
+ const target = change.target as HTMLElement
5
+ const src = target.getAttribute('data-embed-src')
6
+ if (src) {
7
+ loadScript(target, src)
8
+ }
9
+
10
+ if (change.addedNodes) {
11
+ change.addedNodes.forEach((node) => {
12
+ if (node.nodeType == 1) autoloadScripts(node as HTMLElement, false)
13
+ })
14
+ }
15
+ })
16
+ })
17
+ observer.observe(element, { attributes: true, subtree: true, childList: true })
18
+ return observer
19
+ }
20
+
21
+ export function autoloadScripts(element: HTMLElement, observe = true) {
22
+ const embeds = element.querySelectorAll('[data-embed-src]')
23
+ for (var i = 0; i < embeds.length; i++) {
24
+ const embed = embeds[i]
25
+ loadScript(embed as HTMLElement, embed.getAttribute('data-embed-src'))
26
+ }
27
+ if (observe) return observeDOMElement(element)
28
+ }
29
+
30
+ export function loadScript(element: HTMLElement, src: string) {
31
+ for (var p = element; p; p = p.parentElement) {
32
+ if (!p.parentElement) {
33
+ const scripts = p.querySelectorAll('script')
34
+ for (var i = 0; i < scripts.length; i++) {
35
+ if (scripts[i].getAttribute('src') == src) {
36
+ return true
37
+ }
38
+ }
39
+ if (p.tagName == 'HTML') p = p.querySelector('head') || p
40
+ const script = p.ownerDocument.createElement('script')
41
+ script.type = 'module'
42
+ script.src = src
43
+ p.appendChild(script)
44
+ break
45
+ }
46
+ }
47
+ }