@sitecore-feaas/clientside 0.1.27 → 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.
- package/build/cdn.js +115 -0
- package/build/cdn.js.map +1 -0
- package/build/components/FEAASBase.js +212 -79
- package/build/components/FEAASBase.js.map +1 -1
- package/build/components/FEAASComponent.js +51 -17
- package/build/components/FEAASComponent.js.map +1 -1
- package/build/components/FEAASLoader.js +6 -0
- package/build/components/FEAASLoader.js.map +1 -1
- package/build/components/FEAASStylesheet.js +18 -13
- package/build/components/FEAASStylesheet.js.map +1 -1
- package/build/dom/component.js +34 -0
- package/build/dom/component.js.map +1 -0
- package/build/dom/data.js +105 -0
- package/build/dom/data.js.map +1 -0
- package/build/{dom.js → dom/rendering.js} +3 -194
- package/build/dom/rendering.js.map +1 -0
- package/build/dom/scripts.js +48 -0
- package/build/dom/scripts.js.map +1 -0
- package/build/dom/stylesheet.js +39 -0
- package/build/dom/stylesheet.js.map +1 -0
- package/build/fetch.js +72 -22
- package/build/fetch.js.map +1 -1
- package/build/{basic.js → headless.js} +11 -3
- package/build/headless.js.map +1 -0
- package/build/ui/FEAASContext.js +10 -3
- package/build/ui/FEAASContext.js.map +1 -1
- package/build/ui/FEAASEditor.js +5 -0
- package/build/ui/FEAASEditor.js.map +1 -1
- package/build/ui/FEAASPicker.js +7 -1
- package/build/ui/FEAASPicker.js.map +1 -1
- package/build/ui/index.js +1 -1
- package/build/ui/index.js.map +1 -1
- package/build/ui/react.js +6 -0
- package/build/ui/react.js.map +1 -0
- package/build/utils/promise.js +26 -0
- package/build/utils/promise.js.map +1 -0
- package/dist/browser/headless.esm.js +69 -0
- package/dist/browser/headless.esm.js.map +1 -0
- package/dist/browser/headless.js +69 -0
- package/dist/browser/headless.js.map +1 -0
- package/dist/browser/index.esm.js +3 -5
- package/dist/browser/index.esm.js.map +1 -1
- package/dist/browser/index.js +5 -5
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/react.js +67 -0
- package/dist/browser/react.js.map +1 -0
- package/dist/node/headless.esm.js +67 -0
- package/dist/node/headless.esm.js.map +7 -0
- package/dist/node/headless.js +67 -0
- package/dist/node/headless.js.map +7 -0
- package/dist/node/index.esm.js +3 -3
- package/dist/node/index.esm.js.map +4 -4
- package/dist/node/index.js +3 -3
- package/dist/node/index.js.map +4 -4
- package/dist/node/react.js +67 -0
- package/dist/node/react.js.map +7 -0
- package/examples/index.html +0 -5
- package/package.json +23 -12
- package/scripts/build.js +17 -6
- package/scripts/test-setup.ts +1 -0
- package/src/cdn.ts +188 -0
- package/src/components/FEAASBase.tsx +234 -82
- package/src/components/FEAASComponent.tsx +59 -21
- package/src/components/FEAASLoader.tsx +4 -0
- package/src/components/FEAASStylesheet.tsx +14 -13
- package/src/dom/component.ts +35 -0
- package/src/dom/data.ts +124 -0
- package/src/{dom.ts → dom/rendering.ts} +4 -218
- package/src/dom/scripts.ts +47 -0
- package/src/dom/stylesheet.ts +48 -0
- package/src/fetch.ts +79 -25
- package/src/{basic.ts → headless.ts} +11 -2
- package/src/ui/FEAASContext.tsx +12 -4
- package/src/ui/FEAASEditor.tsx +5 -0
- package/src/ui/FEAASPicker.tsx +6 -2
- package/src/ui/index.ts +1 -1
- package/src/ui/react.tsx +8 -0
- package/src/utils/promise.ts +34 -0
- package/test/FEAASBase.test.tsx +195 -0
- package/test/FEAASComponent.test.tsx +284 -0
- package/test/FEAASContext.test.tsx +76 -0
- package/test/FEAASPicker.test.tsx +40 -0
- package/test/FEAASStylesheet.test.tsx +159 -0
- package/test/cdn.ts +39 -0
- package/test/{render.ts → dom.test.ts} +12 -6
- package/test/extras.ts +1 -1
- package/test/fetch.ts +254 -0
- package/test/tsconfig.json +4 -1
- package/types/cdn.d.ts +57 -0
- package/types/cdn.d.ts.map +1 -0
- package/types/components/FEAASBase.d.ts +220 -16
- package/types/components/FEAASBase.d.ts.map +1 -1
- package/types/components/FEAASComponent.d.ts +34 -7
- package/types/components/FEAASComponent.d.ts.map +1 -1
- package/types/components/FEAASLoader.d.ts +4 -0
- package/types/components/FEAASLoader.d.ts.map +1 -1
- package/types/components/FEAASStylesheet.d.ts +5 -13
- package/types/components/FEAASStylesheet.d.ts.map +1 -1
- package/types/dom/component.d.ts +6 -0
- package/types/dom/component.d.ts.map +1 -0
- package/types/dom/data.d.ts +15 -0
- package/types/dom/data.d.ts.map +1 -0
- package/types/dom/rendering.d.ts +20 -0
- package/types/dom/rendering.d.ts.map +1 -0
- package/types/dom/scripts.d.ts +4 -0
- package/types/dom/scripts.d.ts.map +1 -0
- package/types/dom/stylesheet.d.ts +8 -0
- package/types/dom/stylesheet.d.ts.map +1 -0
- package/types/fetch.d.ts +25 -11
- package/types/fetch.d.ts.map +1 -1
- package/types/headless.d.ts +24 -0
- package/types/headless.d.ts.map +1 -0
- package/types/tsbuildinfo +1 -1
- package/types/tsbuildinfo_ui +1 -1
- package/types/ui/FEAASContext.d.ts +15 -5
- package/types/ui/FEAASContext.d.ts.map +1 -1
- package/types/ui/FEAASEditor.d.ts +18 -0
- package/types/ui/FEAASEditor.d.ts.map +1 -1
- package/types/ui/FEAASPicker.d.ts +15 -3
- package/types/ui/FEAASPicker.d.ts.map +1 -1
- package/types/ui/index.d.ts +1 -1
- package/types/ui/index.d.ts.map +1 -1
- package/types/ui/react.d.ts +7 -0
- package/types/ui/react.d.ts.map +1 -0
- package/types/utils/promise.d.ts +8 -0
- package/types/utils/promise.d.ts.map +1 -0
- package/build/basic.js.map +0 -1
- package/build/cloud.js +0 -71
- package/build/cloud.js.map +0 -1
- package/build/dom.js.map +0 -1
- package/build/node.js +0 -10
- package/build/node.js.map +0 -1
- package/build/ui/node.js +0 -3
- package/build/ui/node.js.map +0 -1
- package/dist/browser/basic.esm.js +0 -69
- package/dist/browser/basic.esm.js.map +0 -1
- package/dist/browser/basic.js +0 -69
- package/dist/browser/basic.js.map +0 -1
- package/dist/node/basic.esm.js +0 -67
- package/dist/node/basic.esm.js.map +0 -7
- package/dist/node/basic.js +0 -67
- package/dist/node/basic.js.map +0 -7
- package/src/cloud.ts +0 -118
- package/src/node.ts +0 -10
- package/src/ui/node.ts +0 -2
- package/test/components.ts +0 -31
- package/types/basic.d.ts +0 -16
- package/types/basic.d.ts.map +0 -1
- package/types/cloud.d.ts +0 -28
- package/types/cloud.d.ts.map +0 -1
- package/types/dom.d.ts +0 -41
- package/types/dom.d.ts.map +0 -1
- package/types/node.d.ts +0 -4
- package/types/node.d.ts.map +0 -1
- package/types/ui/node.d.ts +0 -3
- package/types/ui/node.d.ts.map +0 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FEAASCDNStylesheetParams,
|
|
3
|
+
fetchAndRevalidateStylesheet,
|
|
4
|
+
getStylesheetURL,
|
|
5
|
+
ValidatableStylesheetCallback
|
|
6
|
+
} from '../cdn.js'
|
|
7
|
+
export function renderStylesheet(props: FEAASCDNStylesheetParams, style?: HTMLStyleElement) {
|
|
8
|
+
renderStylesheetPromise(props, (style ||= document.createElement('style')))
|
|
9
|
+
return style
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function renderStylesheetPromise(props: FEAASCDNStylesheetParams, stylesheet?: HTMLStyleElement) {
|
|
13
|
+
stylesheet ||= document.createElement('style')
|
|
14
|
+
return fetchAndRevalidateStylesheet(props, (cssText, phase) => {
|
|
15
|
+
stylesheet.textContent = cssText
|
|
16
|
+
}).then(() => stylesheet)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function loadStylesheet(props: FEAASCDNStylesheetParams, callback?: ValidatableStylesheetCallback) {
|
|
20
|
+
var foundStylesheet = findStylesheet(props) as HTMLStyleElement
|
|
21
|
+
if (!foundStylesheet) {
|
|
22
|
+
var createdStylesheet = createStylesheet(props)
|
|
23
|
+
}
|
|
24
|
+
return fetchAndRevalidateStylesheet(props, (cssText, phase) => {
|
|
25
|
+
if (createdStylesheet) {
|
|
26
|
+
createdStylesheet.textContent = cssText
|
|
27
|
+
}
|
|
28
|
+
callback?.(cssText, phase)
|
|
29
|
+
}).then(() => foundStylesheet || createdStylesheet)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function loadStylesheetAllowStale(props: FEAASCDNStylesheetParams) {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
loadStylesheet(props, resolve).catch(reject)
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function findStylesheet(props: FEAASCDNStylesheetParams) {
|
|
39
|
+
return document.head.querySelector(`style[data-href="${getStylesheetURL(props)}"]`)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function createStylesheet(props: FEAASCDNStylesheetParams) {
|
|
43
|
+
const url = getStylesheetURL(props)
|
|
44
|
+
const stylesheet = document.createElement('style')
|
|
45
|
+
stylesheet.setAttribute('data-href', url)
|
|
46
|
+
document.head.appendChild(stylesheet)
|
|
47
|
+
return stylesheet
|
|
48
|
+
}
|
package/src/fetch.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ControlledPromise } from './utils/promise.js'
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Utility to cache-bust static files. It relies on reading Date header to detect cached response, so it requires CORS
|
|
3
5
|
* settings to be set.
|
|
@@ -10,60 +12,115 @@
|
|
|
10
12
|
* It works as promise, but also accepts callback to use cached blobs mid-flight and have access to headers. Any network
|
|
11
13
|
* errors will not be caught. Function throws when unable to fetch valid response.
|
|
12
14
|
*/
|
|
15
|
+
const phases = ['initial', 'check', 'updated'] as const
|
|
16
|
+
export type ValidatableRequestPhase = typeof phases[number]
|
|
17
|
+
export type FetchCache = Record<string, FetchCacheRequest>
|
|
13
18
|
export interface ValidatableRequest {
|
|
14
19
|
url: Parameters<typeof fetch>[0]
|
|
15
20
|
options?: Parameters<typeof fetch>[1]
|
|
16
21
|
validator?: (resposne: Response) => boolean
|
|
17
|
-
callback?: (response: Response, status:
|
|
22
|
+
callback?: (response: Response, status: ValidatableRequestPhase) => any
|
|
18
23
|
isCached?: boolean
|
|
19
24
|
response?: Response
|
|
25
|
+
cache?: FetchCache
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type FetchCacheRequest = {
|
|
29
|
+
phase: ValidatableRequestPhase
|
|
30
|
+
initial: ControlledPromise<Response>
|
|
31
|
+
check: ControlledPromise<Response>
|
|
32
|
+
updated: ControlledPromise<Response>
|
|
33
|
+
final: ControlledPromise<Response>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export var fetchCache: Record<string, FetchCacheRequest> = {}
|
|
37
|
+
export function clearFetchCache() {
|
|
38
|
+
fetchCache = {}
|
|
20
39
|
}
|
|
21
40
|
|
|
22
41
|
export async function fetchAndRevalidate(
|
|
23
42
|
url: ValidatableRequest['url'],
|
|
24
43
|
options?: ValidatableRequest['options'],
|
|
25
44
|
validator?: ValidatableRequest['validator'],
|
|
26
|
-
callback?: ValidatableRequest['callback']
|
|
45
|
+
callback?: ValidatableRequest['callback'],
|
|
46
|
+
cache: FetchCache = fetchCache
|
|
27
47
|
) {
|
|
28
48
|
// make request as usual, see if there's cached version of an asset
|
|
29
|
-
return
|
|
30
|
-
.then(
|
|
49
|
+
return fetchWithCacheAwareness({ url, options, validator, callback, cache })
|
|
50
|
+
.then(fetchToValidateCache)
|
|
31
51
|
.then((result) => result.response)
|
|
32
52
|
}
|
|
33
53
|
|
|
34
|
-
export
|
|
35
|
-
|
|
36
|
-
|
|
54
|
+
export function getFetchCache(url: Parameters<typeof fetch>[0], dontCreate = false) {
|
|
55
|
+
const key = String(url).replace('?head', '')
|
|
56
|
+
var cache = fetchCache[key]
|
|
57
|
+
if (!cache && !dontCreate) {
|
|
58
|
+
cache = fetchCache[key] = {
|
|
59
|
+
phase: null,
|
|
60
|
+
initial: ControlledPromise(),
|
|
61
|
+
check: ControlledPromise(),
|
|
62
|
+
updated: ControlledPromise(),
|
|
63
|
+
final: ControlledPromise()
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return cache
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function fetchWithCacheAwareness(
|
|
70
|
+
{ url, options, validator, callback, cache }: ValidatableRequest,
|
|
71
|
+
phase: ValidatableRequestPhase = 'initial'
|
|
37
72
|
) {
|
|
38
73
|
const date = new Date()
|
|
39
|
-
|
|
74
|
+
if (cache != null) {
|
|
75
|
+
var cachedRequest = getFetchCache(url)
|
|
76
|
+
if (phases.indexOf(cachedRequest.phase) >= phases.indexOf(phase)) {
|
|
77
|
+
var promise = cachedRequest[phase]
|
|
78
|
+
} else {
|
|
79
|
+
cachedRequest.phase = phase
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const response = await (promise || fetch(url, options))
|
|
83
|
+
if (!promise && cachedRequest) {
|
|
84
|
+
if (!response.ok) {
|
|
85
|
+
cachedRequest[phase].reject(new Error('Fetch failed'))
|
|
86
|
+
} else {
|
|
87
|
+
cachedRequest[phase].resolve(response)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
40
90
|
if (!response.ok) throw new Error('Fetch failed')
|
|
41
91
|
if (validator?.(response) === false) throw new Error('Response is not valid')
|
|
42
92
|
// see if response was pulled from cache
|
|
43
|
-
const isCached =
|
|
44
|
-
|
|
93
|
+
const isCached =
|
|
94
|
+
new Date(response.headers.get('date')) < date ||
|
|
95
|
+
(promise && phase == 'initial' && (cachedRequest.phase == 'updated' || cachedRequest.phase == 'check'))
|
|
96
|
+
await callback?.(response, phase)
|
|
45
97
|
return {
|
|
46
98
|
url,
|
|
47
99
|
options,
|
|
48
100
|
isCached,
|
|
49
101
|
response,
|
|
50
|
-
validator
|
|
102
|
+
validator,
|
|
103
|
+
callback,
|
|
104
|
+
cache
|
|
51
105
|
}
|
|
52
106
|
}
|
|
53
107
|
|
|
54
|
-
export async function
|
|
55
|
-
|
|
108
|
+
export async function fetchToValidateCache(
|
|
109
|
+
request: ValidatableRequest & Required<Pick<ValidatableRequest, 'response'>>
|
|
110
|
+
) {
|
|
111
|
+
const { url, isCached, options, validator, response, cache } = request
|
|
56
112
|
if (isCached) {
|
|
57
113
|
// check if response was stale by sending quick HEAD request busting cache
|
|
58
|
-
const { response: head } = await
|
|
114
|
+
const { response: head } = await fetchWithCacheAwareness(
|
|
59
115
|
{
|
|
60
|
-
url: url + '?'
|
|
116
|
+
url: url + '?head',
|
|
61
117
|
options: {
|
|
62
118
|
...options,
|
|
63
119
|
method: 'HEAD',
|
|
64
120
|
cache: 'no-cache'
|
|
65
121
|
},
|
|
66
|
-
validator
|
|
122
|
+
validator,
|
|
123
|
+
cache
|
|
67
124
|
},
|
|
68
125
|
'check'
|
|
69
126
|
)
|
|
@@ -71,23 +128,20 @@ export async function refetchAndValidate(request: ValidatableRequest & Required<
|
|
|
71
128
|
// there's a newer version of the file that we may fetch
|
|
72
129
|
if (head.headers.get('last-modified') != response.headers.get('last-modified')) {
|
|
73
130
|
// otherwise the new resource has to be re-fetched
|
|
74
|
-
return
|
|
131
|
+
return fetchBypassingCache(request)
|
|
75
132
|
}
|
|
76
133
|
}
|
|
77
134
|
return request
|
|
78
135
|
}
|
|
79
136
|
|
|
80
|
-
export async function
|
|
81
|
-
|
|
82
|
-
return fetchAndValidate(
|
|
137
|
+
export async function fetchBypassingCache(request: ValidatableRequest) {
|
|
138
|
+
return fetchWithCacheAwareness(
|
|
83
139
|
{
|
|
84
|
-
|
|
140
|
+
...request,
|
|
85
141
|
options: {
|
|
86
|
-
...options,
|
|
142
|
+
...request.options,
|
|
87
143
|
cache: 'reload'
|
|
88
|
-
}
|
|
89
|
-
validator,
|
|
90
|
-
callback
|
|
144
|
+
}
|
|
91
145
|
},
|
|
92
146
|
'updated'
|
|
93
147
|
)
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simplified build of clientside that does not rely on any Frontend UI (like pickers). It refered from Frontend app to
|
|
3
|
+
* avoid circular dependency. Regular clientside build entry point is in ui/index.ts
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
import FEAASElement from './components/FEAASElement.js'
|
|
2
7
|
|
|
3
|
-
export * from './dom.js'
|
|
8
|
+
export * from './dom/rendering.js'
|
|
9
|
+
export * from './dom/data.js'
|
|
10
|
+
export * from './dom/scripts.js'
|
|
11
|
+
export * from './dom/stylesheet.js'
|
|
12
|
+
export * from './dom/component.js'
|
|
4
13
|
export * from './fetch.js'
|
|
5
|
-
export * from './
|
|
14
|
+
export * from './cdn.js'
|
|
6
15
|
export * from './components/FEAASBase.js'
|
|
7
16
|
export * from './components/FEAASElement.js'
|
|
8
17
|
export * from './components/FEAASComponent.js'
|
package/src/ui/FEAASContext.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type * as FEAASContextImports from '@sitecore-feaas/frontend/types/components/providers/ContextProvider.js'
|
|
2
|
+
import type { AuthModel } from '@sitecore-feaas/sdk'
|
|
2
3
|
import { FEAASBase, OptionalExcept } from '../components/FEAASBase.js'
|
|
3
4
|
|
|
4
5
|
export function FEAASContextProps(element: HTMLElement = this) {
|
|
@@ -8,10 +9,11 @@ export function FEAASContextProps(element: HTMLElement = this) {
|
|
|
8
9
|
frontend: element?.getAttribute('frontend') ?? 'https://components.sitecoreclud.io',
|
|
9
10
|
backend: element?.getAttribute('backend') ?? 'https://components-api.sitecoreclud.io',
|
|
10
11
|
cdn: element?.getAttribute('cdn') ?? 'https://feaascompontsapiprod.blob.core.windows.net',
|
|
11
|
-
|
|
12
|
+
auth: null as Partial<AuthModel>,
|
|
13
|
+
isolate: element?.getAttribute('isolate') == null || element?.getAttribute('isolate') == 'true'
|
|
12
14
|
}
|
|
13
15
|
}
|
|
14
|
-
type FEAASContextProps = ReturnType<typeof FEAASContextProps>
|
|
16
|
+
export type FEAASContextProps = ReturnType<typeof FEAASContextProps>
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
19
|
* Context provides shared parameters and react providers for its nested elements. It loads lazily once any of its
|
|
@@ -23,8 +25,12 @@ export class FEAASContext extends FEAASBase<FEAASContextProps, typeof FEAASConte
|
|
|
23
25
|
return FEAASContextProps(this)
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
defaultProps = {
|
|
29
|
+
isolate: true
|
|
30
|
+
} as Partial<FEAASContextProps>
|
|
31
|
+
|
|
26
32
|
load() {
|
|
27
|
-
return import(
|
|
33
|
+
return this.import(this.formatURL(this.props.src, this.props.frontend))
|
|
28
34
|
}
|
|
29
35
|
|
|
30
36
|
isReadyToLoad() {
|
|
@@ -38,11 +44,13 @@ export class FEAASContext extends FEAASBase<FEAASContextProps, typeof FEAASConte
|
|
|
38
44
|
}
|
|
39
45
|
|
|
40
46
|
getRoot(): HTMLElement | FEAASBase<any, any> | ShadowRoot {
|
|
47
|
+
if (this.getAttribute('isolate') === 'false') return this
|
|
41
48
|
return this.shadowRoot || this.attachShadow({ mode: 'open' })
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
reactRootElement: HTMLDivElement
|
|
45
52
|
getReactRootElement() {
|
|
53
|
+
//if (!this.props.isolate) return this.getRoot()
|
|
46
54
|
if (!this.reactRootElement) {
|
|
47
55
|
this.reactRootElement = document.createElement('div')
|
|
48
56
|
this.getRoot().appendChild(this.reactRootElement)
|
|
@@ -81,7 +89,7 @@ export class FEAASContext extends FEAASBase<FEAASContextProps, typeof FEAASConte
|
|
|
81
89
|
backend={this.props.backend}
|
|
82
90
|
libraryId={this.props.library}
|
|
83
91
|
container={this.getRoot()}
|
|
84
|
-
|
|
92
|
+
auth={this.props.auth}
|
|
85
93
|
getTheme={this.getTheme}
|
|
86
94
|
onStatusChange={this.onStatusChange}
|
|
87
95
|
modalProps={{
|
package/src/ui/FEAASEditor.tsx
CHANGED
|
@@ -70,6 +70,10 @@ export class FEAASEditor extends FEAASBase<FEAASEditorProps, typeof FEAASEditorI
|
|
|
70
70
|
|
|
71
71
|
static observedAttributes = ['library', 'component', 'version', 'revision', 'hidden', 'state', 'changed', 'forked']
|
|
72
72
|
|
|
73
|
+
defaultProps = {
|
|
74
|
+
hidden: false
|
|
75
|
+
} as Partial<FEAASEditorProps>
|
|
76
|
+
|
|
73
77
|
getProps() {
|
|
74
78
|
return FEAASEditorProps(this)
|
|
75
79
|
}
|
|
@@ -230,6 +234,7 @@ export class FEAASEditor extends FEAASBase<FEAASEditorProps, typeof FEAASEditorI
|
|
|
230
234
|
}
|
|
231
235
|
|
|
232
236
|
setPlaceholder() {
|
|
237
|
+
if (!this.iframe) return
|
|
233
238
|
const x = this.iframe.clientWidth
|
|
234
239
|
const y = this.iframe.clientHeight
|
|
235
240
|
const rect = this.iframe.getBoundingClientRect()
|
package/src/ui/FEAASPicker.tsx
CHANGED
|
@@ -16,17 +16,21 @@ export function FEAASPickerProps(element: FEAASBase<any, any> = this) {
|
|
|
16
16
|
onContextAttributeSet: undefined as (context: any, attribute: string, value: string) => void
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
-
type FEAASPickerProps = ReturnType<typeof FEAASPickerProps>
|
|
19
|
+
export type FEAASPickerProps = ReturnType<typeof FEAASPickerProps>
|
|
20
20
|
|
|
21
21
|
export class FEAASPicker extends FEAASBase<FEAASPickerProps, typeof FEAASPickerImports> {
|
|
22
22
|
static observedAttributes = ['library', 'mode', 'frontend', 'src', 'hidden', 'variant']
|
|
23
23
|
|
|
24
|
+
defaultProps = {
|
|
25
|
+
hidden: false
|
|
26
|
+
} as Partial<FEAASPickerProps>
|
|
27
|
+
|
|
24
28
|
getProps() {
|
|
25
29
|
return FEAASPickerProps(this)
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
load() {
|
|
29
|
-
return import(
|
|
33
|
+
return this.import(this.formatURL(this.props.src, this.props.frontend))
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
render(
|
package/src/ui/index.ts
CHANGED
package/src/ui/react.tsx
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { FEAASComponentProps } from '../headless.js'
|
|
2
|
+
|
|
3
|
+
export * from './index.js'
|
|
4
|
+
import React from 'react'
|
|
5
|
+
|
|
6
|
+
export function FEAASEmbed({ data, ...props }: FEAASComponentProps & { src: string }) {
|
|
7
|
+
return <feaas-component {...props} data={data ? JSON.stringify(data) : null} />
|
|
8
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type ControlledPromise<T> = Promise<T> & {
|
|
2
|
+
resolve: (...args: any) => void
|
|
3
|
+
reject: (e: Error) => void
|
|
4
|
+
restart: () => ControlledPromise<T>
|
|
5
|
+
first: ControlledPromise<T>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function ControlledPromise<T>(onSuccess?: (result: T) => void, onFailure = (e: Error) => {}) {
|
|
9
|
+
var first: ControlledPromise<T>
|
|
10
|
+
const restart = () => {
|
|
11
|
+
var resolve: (...args: any) => void
|
|
12
|
+
var reject: (e: Error) => void
|
|
13
|
+
const promise = new Promise<T>((success, failure) => {
|
|
14
|
+
resolve = (value: T) => {
|
|
15
|
+
onSuccess?.(value)
|
|
16
|
+
success(value)
|
|
17
|
+
}
|
|
18
|
+
reject = failure
|
|
19
|
+
})
|
|
20
|
+
var controlled: ControlledPromise<T> = Object.assign(promise, {
|
|
21
|
+
resolve,
|
|
22
|
+
reject,
|
|
23
|
+
restart,
|
|
24
|
+
first
|
|
25
|
+
})
|
|
26
|
+
first = controlled
|
|
27
|
+
|
|
28
|
+
// Avoid unhandled rejection
|
|
29
|
+
controlled.catch(onFailure)
|
|
30
|
+
|
|
31
|
+
return controlled
|
|
32
|
+
}
|
|
33
|
+
return restart()
|
|
34
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { describe, it, assert, beforeAll, expect } from 'vitest'
|
|
2
|
+
import { FEAASBase, ReactPayload } from '../src/headless.js'
|
|
3
|
+
import { JSDOM } from 'jsdom'
|
|
4
|
+
|
|
5
|
+
describe('React', function () {
|
|
6
|
+
describe('Error boundary', () => {
|
|
7
|
+
class FEAASErrorTest extends FEAASBase<{ throwAt: string }> {
|
|
8
|
+
static observedAttributes = ['test']
|
|
9
|
+
|
|
10
|
+
log() {}
|
|
11
|
+
|
|
12
|
+
getProps() {
|
|
13
|
+
this.fail('get-props')
|
|
14
|
+
return {
|
|
15
|
+
throw: this.getAttribute('throw') ?? null,
|
|
16
|
+
test: this.getAttribute('test')
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
mount() {
|
|
21
|
+
if (this.fail('before-mount', true)) return
|
|
22
|
+
super.mount()
|
|
23
|
+
if (this.fail('after-mount', true)) {
|
|
24
|
+
super.unmount()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
unmount() {
|
|
29
|
+
this.fail('before-unmount', true)
|
|
30
|
+
super.unmount()
|
|
31
|
+
this.fail('after-unmount', true)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
connectedCallback(): void {
|
|
35
|
+
super.connectedCallback()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
load() {
|
|
39
|
+
this.fail('before-load')
|
|
40
|
+
return Promise.all([import('react'), import('react-dom'), import('react-dom/client')]).then(
|
|
41
|
+
([React, ReactDOM, ReactDOMClient]) => {
|
|
42
|
+
this.fail('after-load')
|
|
43
|
+
return { React, ReactDOM, ReactDOMClient }
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
render({}: typeof this.props, { React }: ReactPayload) {
|
|
49
|
+
const error = console.error
|
|
50
|
+
console.error = () => {}
|
|
51
|
+
requestAnimationFrame(() => (console.error = error))
|
|
52
|
+
this.fail('render')
|
|
53
|
+
return <div>{1}</div>
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
fail(type: string, soft = false) {
|
|
57
|
+
if (
|
|
58
|
+
(this.getAttribute('throw') == 'initial-' + type && !this.initialized) ||
|
|
59
|
+
(this.getAttribute('throw') == 'subsequent-' + type && this.initialized)
|
|
60
|
+
) {
|
|
61
|
+
const e = new Error(this.getAttribute('throw'))
|
|
62
|
+
if (soft) {
|
|
63
|
+
this.onError(e)
|
|
64
|
+
return true
|
|
65
|
+
} else {
|
|
66
|
+
throw e
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
FEAASErrorTest.register('feaas-test-error-boundary')
|
|
72
|
+
const errors = [] as Error[]
|
|
73
|
+
document.addEventListener('feaasError', (e: CustomEvent<Error>) => {
|
|
74
|
+
errors.push(e.detail)
|
|
75
|
+
//e.preventDefault()
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
describe('during initialization', () => {
|
|
79
|
+
it('should render successfuly', async () => {
|
|
80
|
+
document.write('<feaas-test-error-boundary></feaas-test-error-boundary>')
|
|
81
|
+
await (document.body.firstElementChild as FEAASErrorTest).whenRendered
|
|
82
|
+
expect(document.body.innerHTML).toEqual(`<feaas-test-error-boundary><div>1</div></feaas-test-error-boundary>`)
|
|
83
|
+
})
|
|
84
|
+
async function failOn(errorType: string) {
|
|
85
|
+
try {
|
|
86
|
+
document.write(`<feaas-test-error-boundary throw="${errorType}"></feaas-test-error-boundary>`)
|
|
87
|
+
} catch (e) {}
|
|
88
|
+
const component = document.body.firstElementChild as FEAASErrorTest
|
|
89
|
+
|
|
90
|
+
// Initialization promise is rejected
|
|
91
|
+
await Promise.all([
|
|
92
|
+
expect(() => component.whenRendered).rejects.toEqual(new Error(errorType)),
|
|
93
|
+
expect(() => component.whenLoaded).rejects.toEqual(new Error(errorType))
|
|
94
|
+
])
|
|
95
|
+
|
|
96
|
+
// feaasEvent is dispatched and bubbled to dom
|
|
97
|
+
expect(errors.splice(0)).toEqual([new Error(errorType)])
|
|
98
|
+
|
|
99
|
+
// Component is auto-unmounted (but not ejected from dom)
|
|
100
|
+
expect(component.connected).toBe(false)
|
|
101
|
+
expect(document.body.innerHTML).toEqual(
|
|
102
|
+
`<feaas-test-error-boundary throw="${errorType}"></feaas-test-error-boundary>`
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
it('handles error on get-props', () => failOn('initial-get-props'))
|
|
106
|
+
it('handles error on before mount', () => failOn('initial-before-mount'))
|
|
107
|
+
it('handles error on after mount', () => failOn('initial-after-mount'))
|
|
108
|
+
it('handles error on before load', () => failOn('initial-before-load'))
|
|
109
|
+
it('handles error on after load', () => failOn('initial-after-load'))
|
|
110
|
+
})
|
|
111
|
+
describe('during first render', () => {
|
|
112
|
+
it('should render successfuly', async () => {
|
|
113
|
+
document.write('<feaas-test-error-boundary></feaas-test-error-boundary>')
|
|
114
|
+
await (document.body.firstElementChild as FEAASErrorTest).whenRendered
|
|
115
|
+
expect(document.body.innerHTML).toEqual(`<feaas-test-error-boundary><div>1</div></feaas-test-error-boundary>`)
|
|
116
|
+
})
|
|
117
|
+
async function failOn(errorType: string) {
|
|
118
|
+
try {
|
|
119
|
+
document.write(`<feaas-test-error-boundary throw="${errorType}"></feaas-test-error-boundary>`)
|
|
120
|
+
} catch (e) {}
|
|
121
|
+
const component = document.body.firstElementChild as FEAASErrorTest
|
|
122
|
+
|
|
123
|
+
// Initialization promise is rejected
|
|
124
|
+
await Promise.all([
|
|
125
|
+
expect(() => component.whenRendered).rejects.toEqual(new Error(errorType)),
|
|
126
|
+
expect(component.whenLoaded).resolves.toBeTruthy()
|
|
127
|
+
])
|
|
128
|
+
|
|
129
|
+
// feaasEvent is dispatched and bubbled to dom
|
|
130
|
+
expect(errors.splice(0)).toEqual([new Error(errorType)])
|
|
131
|
+
|
|
132
|
+
// Component is auto-unmounted (but not ejected from dom)
|
|
133
|
+
expect(component.connected).toBe(false)
|
|
134
|
+
expect(document.body.innerHTML).toEqual(
|
|
135
|
+
`<feaas-test-error-boundary throw="${errorType}"></feaas-test-error-boundary>`
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
it('handles error on render', () => failOn('initial-render'))
|
|
139
|
+
})
|
|
140
|
+
describe('subsequently', () => {
|
|
141
|
+
it('should render successfuly', async () => {
|
|
142
|
+
document.write('<feaas-test-error-boundary></feaas-test-error-boundary>')
|
|
143
|
+
const component = document.body.firstElementChild as FEAASErrorTest
|
|
144
|
+
await expect(component.whenLoaded).resolves.toBeTruthy()
|
|
145
|
+
component.setAttribute('test', 'lol')
|
|
146
|
+
await expect(component.whenRendered).resolves.toEqual(component)
|
|
147
|
+
expect(document.body.innerHTML).toEqual(
|
|
148
|
+
`<feaas-test-error-boundary test="lol"><div>1</div></feaas-test-error-boundary>`
|
|
149
|
+
)
|
|
150
|
+
})
|
|
151
|
+
async function failOn(errorType: string, callback: (component: FEAASErrorTest) => void) {
|
|
152
|
+
document.write(`<feaas-test-error-boundary throw="${errorType}"></feaas-test-error-boundary>`)
|
|
153
|
+
const component = document.body.firstElementChild as FEAASErrorTest
|
|
154
|
+
|
|
155
|
+
// Initialization promise is resolved
|
|
156
|
+
await expect(component.whenLoaded).resolves.toBeTruthy()
|
|
157
|
+
await expect(component.whenRendered).resolves.toBeTruthy()
|
|
158
|
+
|
|
159
|
+
// No errors were thrown during initialization
|
|
160
|
+
expect(errors.splice(0)).toEqual([])
|
|
161
|
+
|
|
162
|
+
// Component has rendered
|
|
163
|
+
expect(document.body.innerHTML).toEqual(
|
|
164
|
+
`<feaas-test-error-boundary throw="${errorType}"><div>1</div></feaas-test-error-boundary>`
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
// Execute logic after initialization
|
|
168
|
+
callback(component)
|
|
169
|
+
await expect(() => component.whenRendered).rejects.toEqual(new Error(errorType))
|
|
170
|
+
|
|
171
|
+
// feaasEvent is dispatched and bubbled to dom
|
|
172
|
+
expect(errors.splice(0)).toEqual([new Error(errorType)])
|
|
173
|
+
|
|
174
|
+
// Component is auto-unmounted (but not ejected from dom)
|
|
175
|
+
expect(component.connected).toBe(false)
|
|
176
|
+
}
|
|
177
|
+
it('handles error on change', async () => {
|
|
178
|
+
await failOn('subsequent-get-props', (component) => {
|
|
179
|
+
component.setAttribute('test', 'lol')
|
|
180
|
+
})
|
|
181
|
+
expect(document.body.innerHTML).toEqual(
|
|
182
|
+
`<feaas-test-error-boundary throw="subsequent-get-props" test="lol"><div>1</div></feaas-test-error-boundary>`
|
|
183
|
+
)
|
|
184
|
+
})
|
|
185
|
+
it('handles error on render', async () => {
|
|
186
|
+
await failOn('subsequent-render', (component) => {
|
|
187
|
+
component.setAttribute('test', 'lol')
|
|
188
|
+
})
|
|
189
|
+
expect(document.body.innerHTML).toEqual(
|
|
190
|
+
`<feaas-test-error-boundary throw="subsequent-render" test="lol"></feaas-test-error-boundary>`
|
|
191
|
+
)
|
|
192
|
+
})
|
|
193
|
+
})
|
|
194
|
+
})
|
|
195
|
+
})
|