@sitecore-feaas/clientside 0.1.19 → 0.1.21

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 (80) hide show
  1. package/build/basic.js +5 -1
  2. package/build/basic.js.map +1 -1
  3. package/build/cloud.js +40 -7
  4. package/build/cloud.js.map +1 -1
  5. package/build/components/FEAASBase.js +68 -29
  6. package/build/components/FEAASBase.js.map +1 -1
  7. package/build/components/FEAASComponent.js +36 -2
  8. package/build/components/FEAASComponent.js.map +1 -1
  9. package/build/components/FEAASElement.js +19 -0
  10. package/build/components/FEAASElement.js.map +1 -0
  11. package/build/components/FEAASLoader.js +13 -1
  12. package/build/components/FEAASLoader.js.map +1 -1
  13. package/build/components/FEAASStylesheet.js +1 -1
  14. package/build/components/FEAASStylesheet.js.map +1 -1
  15. package/build/dom.js +6 -0
  16. package/build/dom.js.map +1 -1
  17. package/build/fetch.js +48 -45
  18. package/build/fetch.js.map +1 -1
  19. package/build/ui/FEAASContext.js +13 -14
  20. package/build/ui/FEAASContext.js.map +1 -1
  21. package/build/ui/FEAASEditor.js +448 -76
  22. package/build/ui/FEAASEditor.js.map +1 -1
  23. package/build/ui/FEAASPicker.js +9 -59
  24. package/build/ui/FEAASPicker.js.map +1 -1
  25. package/dist/browser/basic.esm.js +5 -5
  26. package/dist/browser/basic.esm.js.map +1 -1
  27. package/dist/browser/basic.js +5 -5
  28. package/dist/browser/basic.js.map +1 -1
  29. package/dist/browser/index.esm.js +5 -10
  30. package/dist/browser/index.esm.js.map +1 -1
  31. package/dist/browser/index.js +5 -10
  32. package/dist/browser/index.js.map +1 -1
  33. package/dist/node/basic.esm.js +3 -3
  34. package/dist/node/basic.esm.js.map +1 -1
  35. package/dist/node/basic.js +3 -3
  36. package/dist/node/basic.js.map +1 -1
  37. package/dist/node/index.esm.js +3 -8
  38. package/dist/node/index.esm.js.map +1 -1
  39. package/dist/node/index.js +3 -8
  40. package/dist/node/index.js.map +1 -1
  41. package/examples/index.html +8 -49
  42. package/package.json +1 -6
  43. package/src/basic.ts +8 -1
  44. package/src/cloud.ts +43 -13
  45. package/src/components/FEAASBase.tsx +82 -32
  46. package/src/components/{FEAASComponent.ts → FEAASComponent.tsx} +38 -2
  47. package/src/components/FEAASElement.tsx +32 -0
  48. package/src/components/{FEAASLoader.ts → FEAASLoader.tsx} +14 -1
  49. package/src/components/{FEAASStylesheet.ts → FEAASStylesheet.tsx} +1 -1
  50. package/src/dom.ts +5 -0
  51. package/src/fetch.ts +82 -51
  52. package/src/ui/FEAASContext.tsx +14 -15
  53. package/src/ui/FEAASEditor.tsx +599 -145
  54. package/src/ui/FEAASPicker.tsx +19 -66
  55. package/test/components.ts +19 -0
  56. package/types/basic.d.ts +3 -0
  57. package/types/basic.d.ts.map +1 -1
  58. package/types/cloud.d.ts +8 -1
  59. package/types/cloud.d.ts.map +1 -1
  60. package/types/components/FEAASBase.d.ts +20 -6
  61. package/types/components/FEAASBase.d.ts.map +1 -1
  62. package/types/components/FEAASComponent.d.ts +8 -1
  63. package/types/components/FEAASComponent.d.ts.map +1 -1
  64. package/types/components/FEAASElement.d.ts +21 -0
  65. package/types/components/FEAASElement.d.ts.map +1 -0
  66. package/types/components/FEAASLoader.d.ts +6 -0
  67. package/types/components/FEAASLoader.d.ts.map +1 -1
  68. package/types/components/FEAASStylesheet.d.ts +3 -3
  69. package/types/components/FEAASStylesheet.d.ts.map +1 -1
  70. package/types/dom.d.ts.map +1 -1
  71. package/types/fetch.d.ts +42 -1
  72. package/types/fetch.d.ts.map +1 -1
  73. package/types/tsbuildinfo +1 -1
  74. package/types/tsbuildinfo_ui +1 -1
  75. package/types/ui/FEAASContext.d.ts +4 -4
  76. package/types/ui/FEAASContext.d.ts.map +1 -1
  77. package/types/ui/FEAASEditor.d.ts +70 -4
  78. package/types/ui/FEAASEditor.d.ts.map +1 -1
  79. package/types/ui/FEAASPicker.d.ts +14 -33
  80. package/types/ui/FEAASPicker.d.ts.map +1 -1
@@ -1,54 +1,13 @@
1
1
  <script type="module">
2
- import FEAAS from '../dist/browser/index.js'
3
- const renderMyComponent = (element, data) =>
4
- FEAAS.renderComponent(
5
- {
6
- componentId: 'our-dominator1',
7
- versionId: 'dominator1-version2',
8
- revision: 'staged',
9
- hostname: 'https://feaascomponentsapidev.blob.core.windows.net',
10
- data: data
11
- },
12
- element
13
- )
2
+ import FEAASElement from '../dist/browser/basic.esm.js'
14
3
 
15
- const stylesheet = FEAAS.renderStylesheet({
16
- libraryId: 'our-wonderful-library'
17
- })
18
- document.head.appendChild(stylesheet)
19
4
 
20
- const element = renderMyComponent(null, {
21
- dominatorData: {
22
- name: 'YarikZ',
23
- goal: 'Explode the planet',
24
- status: 'progressing',
25
- items: [
26
- {
27
- 'title-text': 'Hello world Hello Thomas 2r23r',
28
- html: '<div class="-card--solid-color-cards -fill--light-blue"><button class="-button">Hola</button> <p><a href="#">Link</a> Text</div></p>',
29
- open: true
30
- },
31
- { 'title-text': 'Bye world', html: 'Mystical island' }
32
- ],
33
- items2: [
34
- {
35
- 'title-text': 'Cherry pop',
36
- html: '<div class="-card--solid-color-cards -fill--light-blue"><button class="-button">Hola</button> <p><a href="#">Link</a> Text</div></p>'
37
- },
38
- {
39
- 'title-text': 'Berry',
40
- html: '<div class="-card--solid-color-cards -fill--light-blue"><button class="-button">Hola</button> <p><a href="#">Link</a> Text</div></p>'
41
- },
42
- {
43
- 'title-text': 'Pear',
44
- html: '<div class="-card--solid-color-cards -fill--light-blue"><button class="-button">Hola</button> <a href="#">Link</a> Text</div>',
45
- open: true
46
- },
47
- { 'title-text': 'Coolio', html: 'Mystical island' }
48
- ]
5
+ export class Z extends FEAASElement<{}> {
6
+ renderReact(
7
+ props: { children?: ReactNode },
8
+ payload: { React: typeof import('react'); ReactDOM: typeof import('react-dom') }
9
+ ): ReactNode | ReactElement<any, string | JSXElementConstructor<any>> {
10
+ return
49
11
  }
50
- })
51
- document.body.appendChild(element)
52
-
53
- // renderMyComponent(element, {"user": {"name": "Bob"}})
12
+ }
54
13
  </script>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sitecore-feaas/clientside",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "scripts": {
5
5
  "start": "webpack serve --open",
6
6
  "build": "rm -rf dist;npm run build:types; npm run build:esbuild; npm run build:sourcemaps",
@@ -14,12 +14,7 @@
14
14
  "postpublish": "node ../cdn/upload-package.js clientside $npm_package_version"
15
15
  },
16
16
  "license": "ISC",
17
- "optionalDependencies": {
18
- "react": ">=16.8.0",
19
- "react-dom": ">=16.8.0"
20
- },
21
17
  "peerDependencies": {
22
- "react": ">=16.8.0",
23
18
  "react-dom": ">=16.8.0"
24
19
  },
25
20
  "main": "./dist/browser/index.esm.js",
package/src/basic.ts CHANGED
@@ -1,6 +1,9 @@
1
+ import FEAASElement from './components/FEAASElement.js'
2
+
1
3
  export * from './dom.js'
2
4
  export * from './fetch.js'
3
5
  export * from './components/FEAASBase.js'
6
+ export * from './components/FEAASElement.js'
4
7
  export * from './components/FEAASComponent.js'
5
8
  export * from './components/FEAASLoader.js'
6
9
  export * from './components/FEAASStylesheet.js'
@@ -8,7 +11,11 @@ export * from './components/FEAASStylesheet.js'
8
11
  declare global {
9
12
  interface Window {
10
13
  FEAASLoading: Promise<any>
14
+ FEAASElement: typeof FEAASElement
11
15
  }
12
16
  }
13
17
 
14
- if (typeof window != 'undefined') window.FEAASLoading ??= Promise.resolve()
18
+ if (typeof window != 'undefined') {
19
+ window.FEAASElement = FEAASElement
20
+ window.FEAASLoading ??= Promise.resolve()
21
+ }
package/src/cloud.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { fetchAndRevalidate } from './fetch.js'
1
+ import { fetchAndRevalidate, fetchAndValidate, invalidateRequest, refetchAndValidate } from './fetch.js'
2
2
 
3
3
  export interface CloudTemplate {
4
4
  cdn?: string
@@ -7,6 +7,7 @@ export interface CloudTemplate {
7
7
  component: string
8
8
  version: string
9
9
  revision?: string
10
+ instance?: string
10
11
  }
11
12
  export interface CloudStylesheet {
12
13
  cdn?: string
@@ -36,20 +37,49 @@ export function getStylesheetURL({
36
37
  }
37
38
 
38
39
  export async function fetchComponent(props: CloudTemplate, callback: (html: string) => void) {
39
- return fetchAndRevalidate(
40
- getComponentURL(props),
41
- {
42
- headers: {
43
- Accept: 'text/html'
40
+ const onlyIfNotDeleted = (response: Response) => {
41
+ return response.headers.get('x-ms-meta-versionDeletedAt') == null
42
+ }
43
+ const parseResponse = (response: Response) => {
44
+ response.text().then(callback)
45
+ }
46
+
47
+ // Fetch two requests in parallel: One optimistically checking if component was forked,
48
+ // and another checking original definition of a component
49
+ const results = await Promise.all([
50
+ props.instance &&
51
+ fetchAndValidate({
52
+ options: {
53
+ headers: {
54
+ Accept: 'text/html'
55
+ }
56
+ },
57
+ url: getComponentURL({ ...props, version: props.instance }),
58
+ validator: onlyIfNotDeleted
59
+ }).catch((e) => e as Error),
60
+ fetchAndValidate({ url: getComponentURL(props), validator: onlyIfNotDeleted }).catch((e) => e as Error)
61
+ ])
62
+ const result = results.find((result) => result && 'isCached' in result && result)
63
+ if (!result || result instanceof Error) {
64
+ const error = results.find((result) => result && result instanceof Error)
65
+ throw error || new Error('Can not fetch component')
66
+ }
67
+ if (results[0] == result) {
68
+ console.log('Using fork!', results)
69
+ }
70
+ parseResponse(result.response)
71
+ return refetchAndValidate({ ...result, callback: parseResponse }).catch((e) => {
72
+ if (results[0] == result) {
73
+ invalidateRequest({ ...result, callback: null, validator: null })
74
+ console.log('Fork was deleted')
75
+ // forked version was cached, but it's now deleted
76
+ if (!(results[1] instanceof Error)) {
77
+ parseResponse(results[1].response)
78
+ return refetchAndValidate({ ...results[1], callback: parseResponse })
44
79
  }
45
- },
46
- (response) => {
47
- return response.headers.get('x-ms-meta-versionDeletedAt') == null
48
- },
49
- (response) => {
50
- response.text().then(callback)
51
80
  }
52
- )
81
+ throw e
82
+ })
53
83
  }
54
84
 
55
85
  export async function fetchStylesheet(props: CloudStylesheet, callback: (css: string) => void) {
@@ -1,4 +1,5 @@
1
1
  import { JSXElementConstructor, ReactElement, ReactNode } from 'react'
2
+ import type ReactDOMClient from 'react-dom/client'
2
3
  import type ReactDOM from 'react-dom'
3
4
  import type React from 'react'
4
5
 
@@ -9,6 +10,7 @@ var WebComponent = typeof HTMLElement == 'undefined' ? (class {} as unknown as t
9
10
  interface ReactPayload {
10
11
  React: typeof React
11
12
  ReactDOM: typeof ReactDOM
13
+ ReactDOMClient: typeof ReactDOMClient
12
14
  }
13
15
 
14
16
  export class FEAASBase<Props = any, Payload = any> extends WebComponent {
@@ -27,7 +29,7 @@ export class FEAASBase<Props = any, Payload = any> extends WebComponent {
27
29
  nextRender: number
28
30
 
29
31
  /** Is/was component in the dom? */
30
- connected = null as Event
32
+ connected = false
31
33
 
32
34
  /** Result of lazy loading */
33
35
  payload: Payload
@@ -35,6 +37,12 @@ export class FEAASBase<Props = any, Payload = any> extends WebComponent {
35
37
  refs: Record<string, FEAASBase> = {}
36
38
  descendants: FEAASBase[] = []
37
39
 
40
+ constructor() {
41
+ super()
42
+ this.addEventListener('feaasConnected', this)
43
+ this.addEventListener('feaasDisconnected', this)
44
+ }
45
+
38
46
  /** Attaches shadow root if getShadowRootOptions any settings, or uses element itself */
39
47
  getRoot(): FEAASBase | HTMLElement | ShadowRoot {
40
48
  return this
@@ -47,38 +55,76 @@ export class FEAASBase<Props = any, Payload = any> extends WebComponent {
47
55
 
48
56
  /** Components parses its attributes when connected, and schedules rendeirng */
49
57
  connectedCallback() {
50
- console.log('FEAAS: Connected', this)
51
- this.connected = new Event('feaasConnected', { bubbles: true, composed: true })
52
- this.dispatchEvent(this.connected)
53
- this.addEventListener('feaasConnected', this)
54
- this.addEventListener('feaasDisconnected', this)
58
+ this.initialize()
59
+ }
60
+
61
+ /**
62
+ * Web components are initialized depth-first, making child elements render before its parents. This could here
63
+ * attempts to avoid this and inverts the initialization logic, so that children are initialized in the context of its
64
+ * already-initialized parents.
65
+ *
66
+ * FEAAS components dispatch `feaasConnected` event that allows components to store references to each other. In
67
+ * addition it allows external scripts to listen for initialization of FEAAS components
68
+ */
69
+ initialize() {
70
+ const parent = this.getParent() as FEAASBase
71
+ // Allow parent component to initialize first
72
+ if (parent && !parent.connected) return
73
+ if (this.connected) return
74
+ this.connected = true
75
+ console.log('FEAAS: Connected', this.tagName, this)
76
+ this.dispatchEvent(new Event('feaasConnected', { bubbles: true, composed: true }))
77
+ this.getElements().map((element: FEAASBase) => element.initialize())
55
78
  this.set()
79
+ return true
80
+ }
81
+
82
+ getParent(onlyFeaas = true) {
83
+ for (
84
+ var p: HTMLElement = this;
85
+ (p = p.parentElement || ((p.parentNode instanceof ShadowRoot ? p.parentNode.host : null) as HTMLElement));
86
+
87
+ ) {
88
+ if (!onlyFeaas || p.tagName.startsWith('FEAAS-')) return p
89
+ }
90
+ }
91
+
92
+ getElements(root: HTMLElement | ShadowRoot = this.getRoot(), onlyFeaas = true): HTMLElement[] {
93
+ return Array.from(root.querySelectorAll('*'))
94
+ .map(
95
+ (el) =>
96
+ [
97
+ !onlyFeaas || el.tagName.startsWith('FEAAS-') ? el : null,
98
+ el.shadowRoot ? this.getElements(el.shadowRoot, onlyFeaas) : null
99
+ ] as HTMLElement[]
100
+ )
101
+ .flat()
102
+ .filter(Boolean)
56
103
  }
57
104
 
58
105
  /** Nothing special happens on disconnect, except setting a flag */
59
106
  disconnectedCallback() {
107
+ if (!this.connected) return
60
108
  console.log('FEAAS: Disconnected', this)
61
- this.removeEventListener('feaasConnected', this)
62
- this.removeEventListener('feaasDisconnected', this)
63
109
  this.dispatchEvent(new Event('feaasDisconnected', { bubbles: true, composed: true }))
64
110
  this.connected = null
65
111
  }
66
112
 
67
113
  /** Catch-all event handler that dispatches events to callback */
68
114
  handleEvent(event: Event) {
115
+ const target = event.composedPath()[0] as FEAASBase
69
116
  switch (event.type) {
70
117
  case 'feaasConnected':
71
- this.nestedCallback(event)
118
+ if (target != this) this.nestedCallback(target)
72
119
  break
73
120
  case 'feaasDisconnected':
74
- this.unnestedCallback(event)
121
+ if (target != this) this.unnestedCallback(target)
75
122
  break
76
123
  }
77
124
  }
78
125
 
79
126
  /** Act on children element being removed */
80
- unnestedCallback(event: Event) {
81
- const target = event.composedPath()[0] as FEAASBase
127
+ unnestedCallback(target: FEAASBase) {
82
128
  const index = this.descendants.indexOf(target)
83
129
  if (index == -1) return
84
130
  console.log('FEAAS: Unnest', this, target)
@@ -91,8 +137,7 @@ export class FEAASBase<Props = any, Payload = any> extends WebComponent {
91
137
  }
92
138
 
93
139
  /** Act on children element being added */
94
- nestedCallback(event: Event): boolean | void {
95
- const target = event.composedPath()[0] as FEAASBase
140
+ nestedCallback(target: FEAASBase): boolean | void {
96
141
  const index = this.descendants.indexOf(target)
97
142
  if (index != -1) return
98
143
  this.descendants.push(target)
@@ -100,9 +145,9 @@ export class FEAASBase<Props = any, Payload = any> extends WebComponent {
100
145
  this.refs[target.getComponentName()] = target
101
146
  target.refs.top = this
102
147
  target.refs.parent ||= this
103
- if (target.renderReact) {
148
+ if (target.render) {
104
149
  for (var p: FEAASBase = target.refs.parent; p; p = p.refs.parent) {
105
- if (p.renderReact) {
150
+ if (p.render) {
106
151
  target.refs.parentReact ||= p
107
152
  target.refs.topReact = p
108
153
  }
@@ -125,7 +170,7 @@ export class FEAASBase<Props = any, Payload = any> extends WebComponent {
125
170
  /** Load lazy component & render */
126
171
  async reify() {
127
172
  if (this.payload !== undefined) {
128
- this.render()
173
+ this.update()
129
174
  } else if (this.isReadyToLoad()) {
130
175
  this.payload = null
131
176
  this.load().then(this.onLoad)
@@ -142,7 +187,7 @@ export class FEAASBase<Props = any, Payload = any> extends WebComponent {
142
187
  if (payload == null) return
143
188
 
144
189
  this.payload = payload
145
- this.render()
190
+ this.update()
146
191
  }
147
192
 
148
193
  /**
@@ -150,15 +195,15 @@ export class FEAASBase<Props = any, Payload = any> extends WebComponent {
150
195
  * the component needs to provide React & ReactDOM references in its payload. Web component redefine its render to not
151
196
  * use react at all
152
197
  */
153
- render(nested = false): any {
198
+ update(nested = false): any {
154
199
  console.log('FEAAS: React', this.tagName, this.payload)
155
200
  if (this.payload == null) return
156
- const { React, ReactDOM } = (this.payload as ReactPayload) || {}
201
+ const { React, ReactDOM, ReactDOMClient } = (this.payload as ReactPayload) || {}
157
202
  if (!React) throw new Error(`${this.tagName}: React is not present in payload`)
158
203
  if (!ReactDOM) throw new Error(`${this.tagName}: ReactDOM is not present in payload`)
159
-
160
204
  if (!this.refs.topReact && !this.forceUpdateReact) {
161
- ReactDOM.render(React.createElement(this.getReactWrapper()), this.getReactRoot())
205
+ this.reactRoot ||= ReactDOMClient.createRoot(this.getReactRootElement())
206
+ this.reactRoot.render(React.createElement(this.getReactWrapper(), { z: 'root' }))
162
207
  } else if (this.forceUpdateReact) {
163
208
  this.forceUpdateReact?.()
164
209
  } else {
@@ -178,19 +223,20 @@ export class FEAASBase<Props = any, Payload = any> extends WebComponent {
178
223
  slot: child.getAttribute('slot')
179
224
  })
180
225
  })
181
- return this.renderReact({ ...this.props, children }, this.payload)
226
+ return this.render({ ...this.props, children }, this.payload)
182
227
  }
183
228
 
184
229
  reactWrapper: (props?: any) => ReactNode
230
+ reactRoot: ReactDOMClient.Root
185
231
  getReactWrapper(target: FEAASBase = this, displayName = target.tagName) {
186
232
  const Wrapper = (target.reactWrapper ||= (props?: any) => {
187
- if (target.renderReact) {
233
+ if (target.render) {
188
234
  const { React, ReactDOM } = (target.payload as ReactPayload) || {}
189
235
  const [value, forceUpdateReact] = React.useReducer((r) => r + 1, 0)
190
236
  target.forceUpdateReact = forceUpdateReact
191
- return ReactDOM.createPortal(<>{target.invokeReactRendering()}</>, target.getReactRoot())
237
+ return ReactDOM.createPortal(<>{target.invokeReactRendering()}</>, target.getReactRootElement())
192
238
  } else {
193
- return target.render()
239
+ return target.update()
194
240
  }
195
241
  })
196
242
  Object.assign(Wrapper, {
@@ -209,12 +255,9 @@ export class FEAASBase<Props = any, Payload = any> extends WebComponent {
209
255
  * Render method that is pretty much the same as React Functional component, but with extra second argument for
210
256
  * payload (usually result of import, it is expected to have React in scope).
211
257
  */
212
- renderReact?(
213
- props: React.PropsWithChildren<typeof this.props>,
214
- payload: typeof this.payload
215
- ): ReactNode | ReactElement
258
+ render?(props: React.PropsWithChildren<typeof this.props>, payload: typeof this.payload): ReactNode | ReactElement
216
259
 
217
- getReactRoot(): HTMLElement | ShadowRoot {
260
+ getReactRootElement(): HTMLElement | ShadowRoot {
218
261
  return this.getRoot()
219
262
  }
220
263
 
@@ -256,7 +299,14 @@ export class FEAASBase<Props = any, Payload = any> extends WebComponent {
256
299
  }
257
300
 
258
301
  getContextAttribute(name: string, element = this) {
259
- return element.closest(`[${name}]`)?.getAttribute(name) ?? this.getAttribute(name)
302
+ for (
303
+ var p: HTMLElement = this;
304
+ (p = p.parentElement || ((p.parentNode instanceof ShadowRoot ? p.parentNode.host : null) as HTMLElement));
305
+
306
+ ) {
307
+ if (p.getAttribute(name) != null) return p.getAttribute(name)
308
+ }
309
+ return this.getAttribute(name)
260
310
  }
261
311
 
262
312
  static register(tagName: string) {
@@ -1,6 +1,7 @@
1
1
  import { fetchComponent } from '../cloud.js'
2
2
  import { renderDOMContent, renderDOMElement } from '../dom.js'
3
3
  import { FEAASBase, OptionalExcept } from './FEAASBase.js'
4
+ import { FEAASLoader } from './FEAASLoader.js'
4
5
 
5
6
  export function FEAASComponentsProps(element: FEAASBase = this) {
6
7
  let data: any
@@ -19,12 +20,25 @@ export function FEAASComponentsProps(element: FEAASBase = this) {
19
20
  hostname: element.getAttribute('hostname'),
20
21
  cdn: element.getContextAttribute('cdn'),
21
22
  template: element.getAttribute('template'),
23
+ instance: element.getAttribute('instance'),
24
+ editable: element.getAttribute('editable') != null,
22
25
  suspended: element.getAttribute('suspended') != null
23
26
  }
24
27
  }
25
28
  export type FEAASComponentsProps = ReturnType<typeof FEAASComponentsProps>
26
29
  export class FEAASComponent extends FEAASBase<FEAASComponentsProps, string> {
27
- static observedAttributes = ['library', 'component', 'version', 'revision', 'hostname', 'cdn', 'template', 'data']
30
+ static observedAttributes = [
31
+ 'library',
32
+ 'component',
33
+ 'version',
34
+ 'revision',
35
+ 'hostname',
36
+ 'cdn',
37
+ 'template',
38
+ 'data',
39
+ 'instance',
40
+ 'editable'
41
+ ]
28
42
 
29
43
  needsRefresh: boolean
30
44
  setData(data: any) {
@@ -36,11 +50,33 @@ export class FEAASComponent extends FEAASBase<FEAASComponentsProps, string> {
36
50
  }
37
51
 
38
52
  attributeChangedCallback(name: string, oldValue: string, newValue: string): void {
53
+ if (oldValue === newValue) return
39
54
  this.payload = undefined
40
55
  this.needsRefresh = true
41
56
  super.attributeChangedCallback(name, oldValue, newValue)
42
57
  }
43
58
 
59
+ getEditor(): any {
60
+ return (document.querySelector('feaas-context') as FEAASBase)?.refs.editor
61
+ }
62
+
63
+ constructor() {
64
+ super()
65
+ this.addEventListener('click', () => {
66
+ if (this.props.editable) {
67
+ this.edit()
68
+ }
69
+ })
70
+ }
71
+
72
+ edit() {
73
+ const editor = this.getEditor()
74
+ if (editor?.setTarget(this)) {
75
+ editor.open()
76
+ return true
77
+ }
78
+ }
79
+
44
80
  /* Check if component is a repeated clone, and return the original */
45
81
  getOriginal() {
46
82
  const scope = this.getAttribute('data-path-scope')
@@ -75,7 +111,7 @@ export class FEAASComponent extends FEAASBase<FEAASComponentsProps, string> {
75
111
  }
76
112
  }
77
113
 
78
- render() {
114
+ update() {
79
115
  if (Array.from(this.children).filter((c) => c.tagName != 'BR').length > 0 && !this.needsRefresh) {
80
116
  renderDOMElement(this, this.props.data)
81
117
  } else {
@@ -0,0 +1,32 @@
1
+ import type { ReactNode, ReactElement, JSXElementConstructor, PropsWithChildren } from 'react'
2
+ import type ReactDOMClient from 'react-dom/client'
3
+ import { FEAASBase } from './FEAASBase.js'
4
+
5
+ export type ReactModules = {
6
+ React: typeof React
7
+ ReactDOM: typeof ReactDOM
8
+ ReactDOMClient: typeof ReactDOMClient
9
+ }
10
+ export type ReactJSX = ReactNode | ReactElement<any, string | JSXElementConstructor<any>>
11
+ export type FEAASReactRender<P> = {
12
+ (this: FEAASElement<P>, props: P, payload: ReactModules): ReactJSX
13
+ }
14
+
15
+ export default class FEAASElement<P> extends FEAASBase<P, ReactModules> {
16
+ load() {
17
+ return Promise.all([
18
+ // @ts-ignore
19
+ import('https://cdn.skypack.dev/react'),
20
+ // @ts-ignore
21
+ import('https://cdn.skypack.dev/react-dom'),
22
+ // @ts-ignore
23
+ import('https://cdn.skypack.dev/react-dom/client')
24
+ ]).then(([React, ReactDOM, ReactDOMClient]) => {
25
+ return { React, ReactDOM, ReactDOMClient }
26
+ })
27
+ }
28
+
29
+ render({}: P, { React }: ReactModules): ReactJSX {
30
+ return <></>
31
+ }
32
+ }
@@ -2,6 +2,7 @@ import { FEAASBase } from './FEAASBase.js'
2
2
 
3
3
  export function FEAASLoaderProps(element: HTMLElement = this) {
4
4
  return {
5
+ opacity: element?.getAttribute('opacity') ? parseFloat(element?.getAttribute('opacity')) : 1,
5
6
  hidden: element?.getAttribute('hidden') != null
6
7
  }
7
8
  }
@@ -11,9 +12,14 @@ export class FEAASLoader extends FEAASBase<FEAASLoaderProps, string> {
11
12
  stylesheet: HTMLStyleElement
12
13
  loader: HTMLDivElement
13
14
 
14
- static observedAttributes = ['hidden']
15
+ getProps() {
16
+ return FEAASLoaderProps(this)
17
+ }
18
+
19
+ static observedAttributes = ['hidden', 'opacity']
15
20
 
16
21
  attributeChangedCallback(name: string, oldValue: string, newValue: string): void {
22
+ if (oldValue === newValue) return
17
23
  if (name == 'hidden') {
18
24
  this.style.setProperty('display', 'block', 'important')
19
25
  this.style.setProperty('transition', 'opacity 0.3s', 'important')
@@ -25,9 +31,12 @@ export class FEAASLoader extends FEAASBase<FEAASLoaderProps, string> {
25
31
  this.style.pointerEvents = 'all'
26
32
  }
27
33
  }
34
+ return super.attributeChangedCallback(name, oldValue, newValue)
28
35
  }
29
36
 
30
37
  connectedCallback() {
38
+ this.payload = null
39
+ super.connectedCallback()
31
40
  this.attributeChangedCallback('hidden', this.getAttribute('hidden'), this.getAttribute('hidden'))
32
41
  this.stylesheet = document.createElement('style')
33
42
  this.stylesheet.textContent = `
@@ -101,6 +110,10 @@ export class FEAASLoader extends FEAASBase<FEAASLoaderProps, string> {
101
110
  this.getRoot().appendChild(this.stylesheet)
102
111
  this.getRoot().appendChild(this.loader)
103
112
  }
113
+
114
+ update() {
115
+ this.loader.style.opacity = String(this.props.opacity)
116
+ }
104
117
  }
105
118
 
106
119
  declare global {
@@ -24,7 +24,7 @@ export class FEAASStylesheet extends FEAASBase<FEAASStylesheetProps, string> {
24
24
  fetchStylesheet(this.props, this.onLoad)
25
25
  }
26
26
 
27
- render() {
27
+ update() {
28
28
  if (!this.stylesheet) {
29
29
  this.stylesheet = document.createElement('style')
30
30
  this.appendChild(this.stylesheet)
package/src/dom.ts CHANGED
@@ -310,6 +310,11 @@ export function renderDOMContent(root: HTMLElement, template: string, data?: Dat
310
310
  const newTemplate = config?.processTemplate?.(root, template, data) || template
311
311
  root.innerHTML = newTemplate
312
312
  root.classList.add('-feaas')
313
+ // fixme, jss needs to assign theme classes to component instead
314
+ const anchor = root.closest('.component.feaas') || root
315
+ if (anchor.className.includes('-theme--')) {
316
+ for (const child of root.children) child.className = child.className.replace(/(\s|^)-theme--[^\s]+\s*/g, '$1')
317
+ }
313
318
  return renderDOMElement(root, data, config)
314
319
  }
315
320