@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.
- package/build/basic.js +5 -1
- package/build/basic.js.map +1 -1
- package/build/cloud.js +40 -7
- package/build/cloud.js.map +1 -1
- package/build/components/FEAASBase.js +68 -29
- package/build/components/FEAASBase.js.map +1 -1
- package/build/components/FEAASComponent.js +36 -2
- package/build/components/FEAASComponent.js.map +1 -1
- package/build/components/FEAASElement.js +19 -0
- package/build/components/FEAASElement.js.map +1 -0
- package/build/components/FEAASLoader.js +13 -1
- package/build/components/FEAASLoader.js.map +1 -1
- package/build/components/FEAASStylesheet.js +1 -1
- package/build/components/FEAASStylesheet.js.map +1 -1
- package/build/dom.js +6 -0
- package/build/dom.js.map +1 -1
- package/build/fetch.js +48 -45
- package/build/fetch.js.map +1 -1
- package/build/ui/FEAASContext.js +13 -14
- package/build/ui/FEAASContext.js.map +1 -1
- package/build/ui/FEAASEditor.js +448 -76
- package/build/ui/FEAASEditor.js.map +1 -1
- package/build/ui/FEAASPicker.js +9 -59
- package/build/ui/FEAASPicker.js.map +1 -1
- package/dist/browser/basic.esm.js +5 -5
- package/dist/browser/basic.esm.js.map +1 -1
- package/dist/browser/basic.js +5 -5
- package/dist/browser/basic.js.map +1 -1
- package/dist/browser/index.esm.js +5 -10
- package/dist/browser/index.esm.js.map +1 -1
- package/dist/browser/index.js +5 -10
- package/dist/browser/index.js.map +1 -1
- package/dist/node/basic.esm.js +3 -3
- package/dist/node/basic.esm.js.map +1 -1
- package/dist/node/basic.js +3 -3
- package/dist/node/basic.js.map +1 -1
- package/dist/node/index.esm.js +3 -8
- package/dist/node/index.esm.js.map +1 -1
- package/dist/node/index.js +3 -8
- package/dist/node/index.js.map +1 -1
- package/examples/index.html +8 -49
- package/package.json +1 -6
- package/src/basic.ts +8 -1
- package/src/cloud.ts +43 -13
- package/src/components/FEAASBase.tsx +82 -32
- package/src/components/{FEAASComponent.ts → FEAASComponent.tsx} +38 -2
- package/src/components/FEAASElement.tsx +32 -0
- package/src/components/{FEAASLoader.ts → FEAASLoader.tsx} +14 -1
- package/src/components/{FEAASStylesheet.ts → FEAASStylesheet.tsx} +1 -1
- package/src/dom.ts +5 -0
- package/src/fetch.ts +82 -51
- package/src/ui/FEAASContext.tsx +14 -15
- package/src/ui/FEAASEditor.tsx +599 -145
- package/src/ui/FEAASPicker.tsx +19 -66
- package/test/components.ts +19 -0
- package/types/basic.d.ts +3 -0
- package/types/basic.d.ts.map +1 -1
- package/types/cloud.d.ts +8 -1
- package/types/cloud.d.ts.map +1 -1
- package/types/components/FEAASBase.d.ts +20 -6
- package/types/components/FEAASBase.d.ts.map +1 -1
- package/types/components/FEAASComponent.d.ts +8 -1
- package/types/components/FEAASComponent.d.ts.map +1 -1
- package/types/components/FEAASElement.d.ts +21 -0
- package/types/components/FEAASElement.d.ts.map +1 -0
- package/types/components/FEAASLoader.d.ts +6 -0
- package/types/components/FEAASLoader.d.ts.map +1 -1
- package/types/components/FEAASStylesheet.d.ts +3 -3
- package/types/components/FEAASStylesheet.d.ts.map +1 -1
- package/types/dom.d.ts.map +1 -1
- package/types/fetch.d.ts +42 -1
- package/types/fetch.d.ts.map +1 -1
- package/types/tsbuildinfo +1 -1
- package/types/tsbuildinfo_ui +1 -1
- package/types/ui/FEAASContext.d.ts +4 -4
- package/types/ui/FEAASContext.d.ts.map +1 -1
- package/types/ui/FEAASEditor.d.ts +70 -4
- package/types/ui/FEAASEditor.d.ts.map +1 -1
- package/types/ui/FEAASPicker.d.ts +14 -33
- package/types/ui/FEAASPicker.d.ts.map +1 -1
package/examples/index.html
CHANGED
|
@@ -1,54 +1,13 @@
|
|
|
1
1
|
<script type="module">
|
|
2
|
-
import
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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.
|
|
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')
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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 =
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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(
|
|
118
|
+
if (target != this) this.nestedCallback(target)
|
|
72
119
|
break
|
|
73
120
|
case 'feaasDisconnected':
|
|
74
|
-
this.unnestedCallback(
|
|
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(
|
|
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(
|
|
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.
|
|
148
|
+
if (target.render) {
|
|
104
149
|
for (var p: FEAASBase = target.refs.parent; p; p = p.refs.parent) {
|
|
105
|
-
if (p.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
237
|
+
return ReactDOM.createPortal(<>{target.invokeReactRendering()}</>, target.getReactRootElement())
|
|
192
238
|
} else {
|
|
193
|
-
return target.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 = [
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|