@sitecore-feaas/clientside 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/README.md +51 -31
  2. package/build/components/FEAASComponent.js +2 -2
  3. package/build/components/FEAASComponent.js.map +1 -1
  4. package/build/components/FEAASElement.js +408 -37
  5. package/build/components/FEAASElement.js.map +1 -1
  6. package/build/components/FEAASLoader.js +2 -2
  7. package/build/components/FEAASLoader.js.map +1 -1
  8. package/build/components/FEAASReactElement.js +47 -0
  9. package/build/components/FEAASReactElement.js.map +1 -0
  10. package/build/components/FEAASStylesheet.js +2 -2
  11. package/build/components/FEAASStylesheet.js.map +1 -1
  12. package/build/headless.js +4 -3
  13. package/build/headless.js.map +1 -1
  14. package/build/ui/FEAASContext.js +14 -5
  15. package/build/ui/FEAASContext.js.map +1 -1
  16. package/build/ui/FEAASEditor.js +2 -2
  17. package/build/ui/FEAASEditor.js.map +1 -1
  18. package/build/ui/FEAASExternal.js +65 -23
  19. package/build/ui/FEAASExternal.js.map +1 -1
  20. package/build/ui/FEAASPicker.js +3 -2
  21. package/build/ui/FEAASPicker.js.map +1 -1
  22. package/dist/browser/headless.cjs +5 -5
  23. package/dist/browser/headless.cjs.map +4 -4
  24. package/dist/browser/headless.esm.js +5 -5
  25. package/dist/browser/headless.esm.js.map +1 -1
  26. package/dist/browser/index.cjs +7 -7
  27. package/dist/browser/index.cjs.map +4 -4
  28. package/dist/browser/index.esm.js +7 -7
  29. package/dist/browser/index.esm.js.map +1 -1
  30. package/dist/browser/react.cjs +6 -6
  31. package/dist/browser/react.cjs.map +4 -4
  32. package/dist/browser/react.esm.js +6 -6
  33. package/dist/browser/react.esm.js.map +1 -1
  34. package/dist/node/headless.cjs +3 -3
  35. package/dist/node/headless.cjs.map +4 -4
  36. package/dist/node/headless.esm.js +2 -2
  37. package/dist/node/headless.esm.js.map +4 -4
  38. package/dist/node/index.cjs +4 -4
  39. package/dist/node/index.cjs.map +4 -4
  40. package/dist/node/index.esm.js +3 -3
  41. package/dist/node/index.esm.js.map +4 -4
  42. package/dist/node/react.cjs +4 -4
  43. package/dist/node/react.cjs.map +4 -4
  44. package/dist/node/react.esm.js +4 -4
  45. package/dist/node/react.esm.js.map +4 -4
  46. package/headless.cjs +3 -3
  47. package/headless.cjs.map +4 -4
  48. package/headless.js +3 -3
  49. package/headless.js.map +4 -4
  50. package/package.json +10 -2
  51. package/react.cjs +6 -6
  52. package/react.cjs.map +4 -4
  53. package/react.js +6 -6
  54. package/react.js.map +4 -4
  55. package/scripts/test-setup.ts +1 -0
  56. package/src/components/FEAASComponent.tsx +4 -4
  57. package/src/components/FEAASElement.tsx +467 -42
  58. package/src/components/FEAASLoader.tsx +2 -2
  59. package/src/components/FEAASReactElement.tsx +61 -0
  60. package/src/components/FEAASStylesheet.tsx +3 -3
  61. package/src/headless.ts +5 -4
  62. package/src/ui/FEAASContext.tsx +16 -7
  63. package/src/ui/FEAASEditor.tsx +7 -7
  64. package/src/ui/FEAASExternal.tsx +76 -23
  65. package/src/ui/FEAASPicker.tsx +4 -3
  66. package/src/ui/tsconfig.json +1 -1
  67. package/test/FEAASBase.test.tsx +2 -2
  68. package/test/FEAASComponent.test.tsx +3 -1
  69. package/test/FEAASContext.test.tsx +206 -29
  70. package/test/FEAASPicker.test.tsx +4 -2
  71. package/types/components/FEAASComponent.d.ts +3 -3
  72. package/types/components/FEAASComponent.d.ts.map +1 -1
  73. package/types/components/FEAASElement.d.ts +292 -23
  74. package/types/components/FEAASElement.d.ts.map +1 -1
  75. package/types/components/FEAASLoader.d.ts +2 -2
  76. package/types/components/FEAASLoader.d.ts.map +1 -1
  77. package/types/components/FEAASReactElement.d.ts +30 -0
  78. package/types/components/FEAASReactElement.d.ts.map +1 -0
  79. package/types/components/FEAASStylesheet.d.ts +3 -3
  80. package/types/components/FEAASStylesheet.d.ts.map +1 -1
  81. package/types/headless.d.ts +4 -3
  82. package/types/headless.d.ts.map +1 -1
  83. package/types/tsbuildinfo +1 -1
  84. package/types/ui/FEAASContext.d.ts +8 -7
  85. package/types/ui/FEAASContext.d.ts.map +1 -1
  86. package/types/ui/FEAASEditor.d.ts +6 -6
  87. package/types/ui/FEAASEditor.d.ts.map +1 -1
  88. package/types/ui/FEAASExternal.d.ts +8 -10
  89. package/types/ui/FEAASExternal.d.ts.map +1 -1
  90. package/types/ui/FEAASPicker.d.ts +7 -6
  91. package/types/ui/FEAASPicker.d.ts.map +1 -1
  92. package/types/ui/tsbuildinfo +1 -0
  93. package/build/components/FEAASBase.js +0 -416
  94. package/build/components/FEAASBase.js.map +0 -1
  95. package/src/components/FEAASBase.tsx +0 -485
  96. package/types/components/FEAASBase.d.ts +0 -299
  97. package/types/components/FEAASBase.d.ts.map +0 -1
  98. package/types/tsbuildinfo_ui +0 -1
@@ -1,485 +0,0 @@
1
- import type { JSXElementConstructor, ReactElement, ReactNode } from 'react'
2
- import type ReactDOMClient from 'react-dom/client'
3
- import type ReactDOM from 'react-dom'
4
- import type React from 'react'
5
- import { ControlledPromise } from '../utils/promise.js'
6
-
7
- export type OptionalExcept<T, K extends keyof T> = Pick<T, K> & Partial<Omit<T, K>>
8
-
9
- // Shim in case it's required in node.js environment
10
- var WebComponent =
11
- typeof HTMLElement == 'undefined'
12
- ? // @ts-ignore
13
- typeof windowJSDOM != 'undefined'
14
- ? // @ts-ignore
15
- (windowJSDOM.HTMLElement as typeof HTMLElement)
16
- : (class {} as unknown as typeof HTMLElement)
17
- : HTMLElement
18
-
19
- export interface ReactPayload {
20
- React: typeof React
21
- ReactDOM: typeof ReactDOM
22
- ReactDOMClient: typeof ReactDOMClient
23
- }
24
- /**
25
- * FEAASBase is a superclass for FEAAS components with optional react support.
26
- *
27
- * Initialization lifecycle:
28
- *
29
- * - `set(props)`
30
- * - `scheduleRender()` - any attribute change causes render to schedule to next frame
31
- * - `whenRendered.restart()` - rendering promise is reset and will resolve after render
32
- * - `advance()` - called on next frame
33
- * - `if (isReadyToLoad()) => boolean` - check if component has all necessary properties to load
34
- * - `load() => payload` => component is loaded
35
- * - `onLoad?(payload)` => invoke loaded callback
36
- * - `advance()` => going further in state machine
37
- * - `flush()` => flushes all react changes if needed
38
- * - `update()` => component-specific method (or react) to render changes to dom
39
- * - `render(payload, props)` => render means react
40
- * - `whenRendered.resolve()` - rendering promise is resolved after rendering
41
- * - `whenLoaded.resolved()` - resolved after loading and rendering
42
- */
43
- export class FEAASBase<Props = any, Payload = any> extends WebComponent {
44
- static observedAttributes: string[]
45
-
46
- defaultProps = {} as Partial<Props>
47
-
48
- /** Return attributes as parsed values */
49
- getProps(context?: HTMLElement): any {
50
- return {}
51
- }
52
-
53
- /** Properties parsed in current tick */
54
- props: Props
55
- overrides: Props
56
-
57
- /** Timer until next tick */
58
- nextRender: number
59
-
60
- /** Is/was component in the dom? */
61
- connected = false
62
-
63
- refs: Record<string, FEAASBase> = {}
64
- descendants: FEAASBase[] = []
65
-
66
- /** Result of lazy loading */
67
- payload: Payload
68
- whenLoaded = ControlledPromise<Payload>((payload) => {
69
- this.payload = payload
70
- })
71
-
72
- constructor() {
73
- super()
74
- this.addEventListener('feaasMount', this)
75
- this.addEventListener('feaasUnmount', this)
76
- }
77
-
78
- /** Attaches shadow root if getShadowRootOptions any settings, or uses element itself */
79
- getRoot(): FEAASBase | HTMLElement | ShadowRoot {
80
- return this
81
- }
82
-
83
- /** Attribute changes trigger props parsing and schedules re-rendering */
84
- attributeChangedCallback(name: string, oldValue: string, newValue: string) {
85
- this.set()
86
- }
87
-
88
- /** Components parses its attributes when connected, and schedules rendeirng */
89
- connectedCallback() {
90
- this.mount()
91
- }
92
- disconnectedCallback() {
93
- this.unmount()
94
- }
95
- /**
96
- * Web components are initialized depth-first, making child elements render before its parents. This could here
97
- * attempts to avoid this and inverts the initialization logic, so that children are initialized in the context of its
98
- * already-initialized parents.
99
- *
100
- * FEAAS components dispatch `feaasMount` event that allows components to store references to each other. In addition
101
- * it allows external scripts to listen for initialization of FEAAS components
102
- */
103
- mount() {
104
- try {
105
- const parent = this.getParent() as FEAASBase
106
- // Allow parent component to initialize first
107
- if (parent && !parent.connected) return
108
- if (this.connected) return
109
- this.connected = true
110
- this.log('FEAAS: Mount', this.tagName, this)
111
- this.dispatchEvent(new Event('feaasMount', { bubbles: true, composed: true }))
112
- this.getElements().map((element: FEAASBase) => element.mount?.())
113
- this.set()
114
- } catch (e) {
115
- this.onError(e)
116
- }
117
- }
118
-
119
- log(...args: any) {
120
- console.log(...args)
121
- }
122
-
123
- /** Nothing special happens on unmount, except setting a flag */
124
- unmount() {
125
- if (!this.connected) return
126
- this.log('FEAAS: Unmount', this)
127
- this.dispatchEvent(new Event('feaasUnmount', { bubbles: true, composed: true }))
128
- this.connected = false
129
- }
130
-
131
- getParent(onlyFeaas = true) {
132
- for (
133
- var p: HTMLElement = this;
134
- (p = p.parentElement || ((p.parentNode instanceof ShadowRoot ? p.parentNode.host : null) as HTMLElement));
135
-
136
- ) {
137
- if (!onlyFeaas || p.tagName.startsWith('FEAAS-')) return p
138
- }
139
- }
140
-
141
- getElements(root: HTMLElement | ShadowRoot = this.getRoot(), onlyFeaas = true): HTMLElement[] {
142
- return Array.from(root.querySelectorAll('*'))
143
- .map(
144
- (el) =>
145
- [
146
- !onlyFeaas || el.tagName.startsWith('FEAAS-') ? el : null,
147
- el.shadowRoot ? this.getElements(el.shadowRoot, onlyFeaas) : null
148
- ] as HTMLElement[]
149
- )
150
- .flat()
151
- .filter(Boolean)
152
- }
153
-
154
- /** Catch-all event handler that dispatches events to callback */
155
- handleEvent(event: Event) {
156
- const target = event.composedPath()[0] as FEAASBase
157
- switch (event.type) {
158
- case 'feaasMount':
159
- if (target != this) this.nestedCallback(target)
160
- break
161
- case 'feaasUnmount':
162
- if (target != this) this.unnestedCallback(target)
163
- break
164
- }
165
- }
166
-
167
- /** Act on children element being removed */
168
- unnestedCallback(target: FEAASBase) {
169
- const index = this.descendants.indexOf(target)
170
- if (index == -1) return
171
- this.log('FEAAS: Unnest', this, target)
172
- this.descendants.splice(index, -1)
173
- this.refs[target.getComponentName()] = null
174
- if (target.refs.parent == this) target.refs.parent = null
175
- if (target.refs.top == this) {
176
- target.refs.top = target.refs.parent
177
- }
178
- }
179
-
180
- /** Act on children element being added */
181
- nestedCallback(target: FEAASBase): boolean | void {
182
- const index = this.descendants.indexOf(target)
183
- if (index != -1) return
184
- this.descendants.push(target)
185
- this.log('FEAAS: Nest', this, target)
186
- this.refs[target.getComponentName()] = target
187
- target.refs.top = this
188
- target.refs.parent ||= this
189
- if (target.render) {
190
- for (var p: FEAASBase = target.refs.parent; p; p = p.refs.parent) {
191
- if (p.render) {
192
- target.refs.parentReact ||= p
193
- target.refs.topReact = p
194
- }
195
- }
196
- }
197
- }
198
-
199
- getComponentName() {
200
- return this.tagName.replace('FEAAS-', '').toLowerCase()
201
- }
202
-
203
- /**
204
- * Check if lazy component is ready to be loaded. For example a component may stay unloaded unless certain attributes
205
- * are set
206
- */
207
- isReadyToLoad() {
208
- return this.payload === undefined && this.getAttribute('hidden') == null
209
- }
210
-
211
- /** Generic error handler */
212
- onError(error: Error, where: string = 'uncaught', handled = false) {
213
- const event = new CustomEvent('feaasError', { bubbles: true, composed: true, detail: error })
214
- this.dispatchEvent(event)
215
- this.whenLoaded.reject(error)
216
- if (!event.defaultPrevented && !handled) {
217
- this.whenRendered.reject(error)
218
- try {
219
- this.unmount()
220
- } catch (e) {}
221
- return true
222
- }
223
- return false
224
- }
225
-
226
- shouldUpdateOnLoad() {
227
- return true
228
- }
229
-
230
- onLoad: (payload: Payload) => void
231
-
232
- /** Load lazy component & render */
233
- async advance() {
234
- try {
235
- if (this.payload === undefined) {
236
- if (!this.isReadyToLoad()) return
237
- this.payload = null
238
- this.dispatchEvent(new Event('feaasLoad', { bubbles: true, composed: true }))
239
- const loaded = await this.load().catch((e) => {
240
- this.payload = undefined
241
- throw e
242
- })
243
-
244
- this.whenLoaded.resolve(loaded)
245
-
246
- // Allow component to handle the post-loading
247
- if (this.onLoad) {
248
- return
249
- }
250
- } else if (this.payload == null) {
251
- return
252
- }
253
- this.flush()
254
- this.whenRendered.resolve(this)
255
- } catch (e) {
256
- this.onError(e)
257
- }
258
- }
259
-
260
- flush() {
261
- const flushSync = (this.payload as ReactPayload)?.ReactDOM?.flushSync
262
- if (flushSync) {
263
- flushSync(() => this.update())
264
- } else {
265
- this.update()
266
- }
267
- }
268
-
269
- /** Invoke preloading logic that needs to finish before component can render */
270
- load() {
271
- return Promise.resolve(null)
272
- }
273
-
274
- /**
275
- * Component-specific logic that updates the DOM. Default render implementation assume react rendering, in that case
276
- * the component needs to provide React & ReactDOM references in its payload. Web component redefine its render to not
277
- * use react at all
278
- */
279
- update(): any {
280
- //this.log('FEAAS: React', this.tagName, this.payload)
281
- if (this.payload == null) return
282
- if (!this.render) return
283
- const { React, ReactDOM, ReactDOMClient } = (this.payload as ReactPayload) || {}
284
- if (!React) throw new Error(`${this.tagName}: React is not present in payload`)
285
- if (!ReactDOM) throw new Error(`${this.tagName}: ReactDOM is not present in payload`)
286
- if (!this.refs.topReact && !this.forceUpdateReact) {
287
- if (ReactDOMClient) {
288
- this.reactRoot ||= ReactDOMClient.createRoot(this.getReactRootElement())
289
- this.reactRoot.render(this.getReactElement())
290
- } else {
291
- ReactDOM.render(this.getReactElement(), this.getReactRootElement())
292
- }
293
- } else if (this.forceUpdateReact) {
294
- this.forceUpdateReact?.()
295
- } else {
296
- this.refs.topReact.forceUpdateReact?.()
297
- }
298
- }
299
-
300
- invokeReactRendering(): ReactNode {
301
- const { React, ReactDOM } = (this.payload as ReactPayload) || {}
302
- if (!this.connected) return null
303
- const children = this.descendants
304
- .filter((r) => {
305
- return r.refs.parentReact == this && r.payload
306
- })
307
- .map((child: FEAASBase<any, any>, index) => {
308
- return this.getReactElement(child)
309
- })
310
- return this.render({ ...this.props, children }, this.payload)
311
- }
312
-
313
- reactWrapper: React.FunctionComponent
314
- reactErrorBoundary: new (...args: any) => React.Component<{ children: any; slot: string }, { error: Error }>
315
- reactRoot: ReactDOMClient.Root
316
-
317
- getReactErrorBoundary(target: FEAASBase = this, displayName = target.tagName) {
318
- if (this.reactErrorBoundary) return this.reactErrorBoundary
319
-
320
- const { React, ReactDOM } = (target.payload as ReactPayload) || {}
321
- return (this.reactErrorBoundary ||= class FEAASErrorBoundary extends React.Component<
322
- { children: any; slot: string },
323
- { error: Error }
324
- > {
325
- state = {
326
- error: null as Error
327
- }
328
-
329
- static getDerivedStateFromError(error: Error) {
330
- return { error: error }
331
- }
332
-
333
- componentDidCatch(error: Error, errorInfo: any) {
334
- console.error(`FEAAS: ${target.tagName} Error:`, error, errorInfo)
335
- target.onError(error, 'react')
336
- //this.setState({ error: error })
337
- }
338
-
339
- render() {
340
- if (this.state.error) {
341
- // target.classList.add('feaas-error')
342
- return
343
- //return <h1>There seems to be a problem.</h1>
344
- }
345
- //if (target.classList.contains('feaas-error')) target.classList.remove('feaas-error')
346
-
347
- return this.props.children
348
- }
349
- })
350
- }
351
-
352
- getReactElement(target: FEAASBase = this, props?: any) {
353
- const { React, ReactDOM } = (target.payload as ReactPayload) || {}
354
- const ErrorBoundary = this.getReactErrorBoundary()
355
- const Wrapper = this.getReactWrapper(target)
356
- return (
357
- <ErrorBoundary key={target.tagName + '-' + target.getUID()} slot={target.getAttribute('slot')}>
358
- <Wrapper />
359
- </ErrorBoundary>
360
- )
361
- }
362
-
363
- getReactWrapper(target: FEAASBase = this, displayName = target.tagName) {
364
- if (target.reactWrapper) return target.reactWrapper
365
-
366
- const { React, ReactDOM } = (target.payload as ReactPayload) || {}
367
-
368
- target.reactWrapper = (props?: any) => {
369
- if (target.render) {
370
- const [value, forceUpdateReact] = React.useReducer((r) => r + 1, 0)
371
- target.forceUpdateReact = forceUpdateReact
372
- return ReactDOM.createPortal(<>{target.invokeReactRendering()}</>, target.getReactRootElement())
373
- } else {
374
- return target.update()
375
- }
376
- }
377
-
378
- Object.assign(target.reactWrapper, {
379
- key: target.tagName + '-' + target.getUID(),
380
- displayName: displayName
381
- })
382
-
383
- return target.reactWrapper
384
- }
385
-
386
- uid: string
387
- getUID() {
388
- return (this.uid ||= String(Math.random()))
389
- }
390
- forceUpdateReact: (node?: ReactNode) => void
391
-
392
- /**
393
- * Render method that is pretty much the same as React Functional component, but with extra second argument for
394
- * payload (usually result of import, it is expected to have React in scope).
395
- */
396
- render?(props: React.PropsWithChildren<Props>, payload: Payload): ReactNode | ReactElement
397
-
398
- useShadowRoot: boolean
399
- getShadowRoot() {
400
- if (!this.shadowRoot) {
401
- this.attachShadow({ mode: 'open' })
402
- }
403
- return this.shadowRoot
404
- }
405
- getReactRootElement(): HTMLElement | ShadowRoot {
406
- return this.useShadowRoot ? this.getShadowRoot() : this.getRoot()
407
- }
408
-
409
- /** Set props and return values on new next tick */
410
- set(overrides?: Partial<Props>) {
411
- try {
412
- this.scheduleRender()
413
- this.overrides = {
414
- ...this.overrides,
415
- ...overrides
416
- }
417
- this.props = {
418
- ...this.getProps(),
419
- ...this.overrides
420
- }
421
- for (var property in this.props) {
422
- const value = this.props[property]
423
- const attribute = property.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
424
- if (value == null || value === this.defaultProps[property as keyof typeof this.defaultProps]) {
425
- this.removeAttribute(attribute)
426
- } else if (typeof value == 'string' || typeof value == 'number' || typeof value == 'boolean') {
427
- if (this.getAttribute(attribute) != String(value)) this.setAttribute(attribute, String(value))
428
- }
429
- }
430
- } catch (e) {
431
- this.whenRendered.reject(e)
432
- this.onError(e, 'set')
433
- }
434
- return this
435
- }
436
-
437
- import(path: string): Promise<Payload> {
438
- return import(/* @vite-ignore */ /* webpackIgnore: true */ path)
439
- }
440
-
441
- /** Schedule rendering on next tick, to batch multiple attribute changes */
442
- initialized: boolean
443
- whenRendered = ControlledPromise<this>(
444
- () => (this.initialized = true),
445
- () => cancelAnimationFrame(this.nextRender)
446
- )
447
- scheduleRender() {
448
- if (this.nextRender == null) {
449
- this.whenRendered = this.whenRendered.restart()
450
- }
451
- cancelAnimationFrame(this.nextRender)
452
- this.nextRender = requestAnimationFrame(() => {
453
- this.nextRender = null
454
- this.advance().catch((e) => {})
455
- })
456
- }
457
-
458
- formatURL(src: string, hostname: string) {
459
- if (typeof src == 'string' && src.match(/^\/[^\/]/)) {
460
- return hostname + src
461
- } else {
462
- return src
463
- }
464
- }
465
-
466
- getContextAttribute(name: string, element = this) {
467
- for (
468
- var p: HTMLElement = this;
469
- (p = p.parentElement || ((p.parentNode instanceof ShadowRoot ? p.parentNode.host : null) as HTMLElement));
470
-
471
- ) {
472
- if (p.getAttribute(name) != null) return p.getAttribute(name)
473
- }
474
- return this.getAttribute(name)
475
- }
476
-
477
- static register(tagName: string, win?: Window) {
478
- if (win == null) win = typeof window != 'undefined' ? window : null
479
- if (win && !win.customElements.get(tagName)) {
480
- win.customElements.define(tagName, this)
481
- }
482
- }
483
- }
484
-
485
- export { FEAASBase as Base }