piral-core 0.14.9 → 0.14.10-beta.3659

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 (48) hide show
  1. package/esm/components/ErrorBoundary.d.ts +12 -33
  2. package/esm/components/ErrorBoundary.js +12 -14
  3. package/esm/components/ErrorBoundary.js.map +1 -1
  4. package/esm/components/ForeignComponentContainer.d.ts +19 -0
  5. package/esm/components/ForeignComponentContainer.js +52 -0
  6. package/esm/components/ForeignComponentContainer.js.map +1 -0
  7. package/esm/components/index.d.ts +1 -0
  8. package/esm/components/index.js +1 -0
  9. package/esm/components/index.js.map +1 -1
  10. package/esm/components/wrapComponent.d.ts +7 -0
  11. package/esm/components/wrapComponent.js +42 -0
  12. package/esm/components/wrapComponent.js.map +1 -0
  13. package/esm/modules/core.js +3 -2
  14. package/esm/modules/core.js.map +1 -1
  15. package/esm/state/withApi.d.ts +1 -1
  16. package/esm/state/withApi.js +17 -103
  17. package/esm/state/withApi.js.map +1 -1
  18. package/esm/types/api.d.ts +6 -2
  19. package/esm/types/extension.d.ts +5 -1
  20. package/lib/components/ErrorBoundary.d.ts +12 -33
  21. package/lib/components/ErrorBoundary.js +12 -14
  22. package/lib/components/ErrorBoundary.js.map +1 -1
  23. package/lib/components/ForeignComponentContainer.d.ts +19 -0
  24. package/lib/components/ForeignComponentContainer.js +56 -0
  25. package/lib/components/ForeignComponentContainer.js.map +1 -0
  26. package/lib/components/index.d.ts +1 -0
  27. package/lib/components/index.js +1 -0
  28. package/lib/components/index.js.map +1 -1
  29. package/lib/components/wrapComponent.d.ts +7 -0
  30. package/lib/components/wrapComponent.js +46 -0
  31. package/lib/components/wrapComponent.js.map +1 -0
  32. package/lib/modules/core.js +3 -2
  33. package/lib/modules/core.js.map +1 -1
  34. package/lib/state/withApi.d.ts +1 -1
  35. package/lib/state/withApi.js +15 -101
  36. package/lib/state/withApi.js.map +1 -1
  37. package/lib/types/api.d.ts +6 -2
  38. package/lib/types/extension.d.ts +5 -1
  39. package/package.json +4 -4
  40. package/src/components/ErrorBoundary.tsx +19 -51
  41. package/src/components/ForeignComponentContainer.tsx +69 -0
  42. package/src/components/index.ts +1 -0
  43. package/src/components/wrapComponent.tsx +74 -0
  44. package/src/modules/core.ts +3 -2
  45. package/src/state/withApi.test.tsx +20 -4
  46. package/src/state/withApi.tsx +31 -157
  47. package/src/types/api.ts +8 -3
  48. package/src/types/extension.ts +12 -3
@@ -0,0 +1,74 @@
1
+ import * as React from 'react';
2
+ import { __RouterContext } from 'react-router';
3
+ import { PortalRenderer } from './PortalRenderer';
4
+ import { ForeignComponentContainer } from './ForeignComponentContainer';
5
+ import { useGlobalStateContext } from '../hooks';
6
+ import { convertComponent, none } from '../utils';
7
+ import { AnyComponent, ComponentConverters, ForeignComponent, PiletApi, BaseComponentProps } from '../types';
8
+
9
+ // this is an arbitrary start number to have 6 digits
10
+ let portalIdBase = 123456;
11
+
12
+ interface CapturedProps {
13
+ piral: PiletApi;
14
+ }
15
+
16
+ function wrapReactComponent<T>(
17
+ Component: React.ComponentType<T & BaseComponentProps>,
18
+ captured: CapturedProps,
19
+ Wrapper: React.FC<T>,
20
+ ): React.ComponentType<T> {
21
+ return (props: T) => (
22
+ <Wrapper {...props}>
23
+ <Component {...props} {...captured} />
24
+ </Wrapper>
25
+ );
26
+ }
27
+
28
+ function wrapForeignComponent<T>(
29
+ component: ForeignComponent<T & BaseComponentProps>,
30
+ captured: CapturedProps,
31
+ Wrapper: React.FC<T>,
32
+ ) {
33
+ return React.memo((props: T) => {
34
+ const { state, readState, destroyPortal } = useGlobalStateContext();
35
+ const router = React.useContext(__RouterContext);
36
+ const id = React.useMemo(() => (portalIdBase++).toString(26), none);
37
+ const context = React.useMemo(() => ({ router, state, readState }), [router, state]);
38
+ const innerProps = React.useMemo(() => ({ ...props, ...captured }), [props]);
39
+
40
+ React.useEffect(() => () => destroyPortal(id), none);
41
+
42
+ return (
43
+ <Wrapper {...props}>
44
+ <PortalRenderer id={id} />
45
+ <ForeignComponentContainer innerProps={innerProps} $portalId={id} $component={component} $context={context} />
46
+ </Wrapper>
47
+ );
48
+ });
49
+ }
50
+
51
+ function isNotExotic(component: any): component is object {
52
+ return !(component as React.ExoticComponent).$$typeof;
53
+ }
54
+
55
+ export function wrapComponent<T>(
56
+ converters: ComponentConverters<T & BaseComponentProps>,
57
+ component: AnyComponent<T & BaseComponentProps>,
58
+ captured: CapturedProps,
59
+ Wrapper: React.FC<T>,
60
+ ) {
61
+ if (!component) {
62
+ const pilet = captured.piral.meta.name;
63
+ console.error(`[${pilet}] The given value is not a valid component.`);
64
+ // tslint:disable-next-line:no-null-keyword
65
+ component = () => null;
66
+ }
67
+
68
+ if (typeof component === 'object' && isNotExotic(component)) {
69
+ const result = convertComponent(converters[component.type], component);
70
+ return wrapForeignComponent<T>(result, captured, Wrapper);
71
+ }
72
+
73
+ return wrapReactComponent<T>(component, captured, Wrapper);
74
+ }
@@ -17,11 +17,12 @@ export function createCoreApi(context: GlobalStateContext): PiletApiExtender<Pil
17
17
  const expiration = getDataExpiration(expires);
18
18
  return context.tryWriteDataItem(name, value, pilet, target, expiration);
19
19
  },
20
- registerPage(route, arg, meta) {
20
+ registerPage(route, arg, meta = {}) {
21
+ const component = withApi(context, arg, api, 'page', undefined, { meta });
21
22
  context.registerPage(route, {
22
23
  pilet,
23
24
  meta,
24
- component: withApi(context, arg, api, 'page'),
25
+ component,
25
26
  });
26
27
  return () => api.unregisterPage(route);
27
28
  },
@@ -58,7 +58,11 @@ StubComponent.displayName = 'StubComponent';
58
58
 
59
59
  describe('withApi Module', () => {
60
60
  it('wraps a component and forwards the API as piral', () => {
61
- const api: any = {};
61
+ const api: any = {
62
+ meta: {
63
+ name: 'foo',
64
+ },
65
+ };
62
66
  const { context } = createMockContainer();
63
67
  const Component = withApi(context, StubComponent, api, 'feed' as any);
64
68
  const node = mount(<Component />);
@@ -67,7 +71,11 @@ describe('withApi Module', () => {
67
71
 
68
72
  it('is protected against a component crash', () => {
69
73
  console.error = jest.fn();
70
- const api: any = {};
74
+ const api: any = {
75
+ meta: {
76
+ name: 'foo',
77
+ },
78
+ };
71
79
  const { context } = createMockContainer();
72
80
  const Component = withApi(context, StubComponent, api, 'feed' as any);
73
81
  const node = mount(<Component shouldCrash />);
@@ -88,7 +96,11 @@ describe('withApi Module', () => {
88
96
  });
89
97
 
90
98
  it('Wraps component of type object', () => {
91
- const api: any = {};
99
+ const api: any = {
100
+ meta: {
101
+ name: 'foo',
102
+ },
103
+ };
92
104
  const { context } = createMockContainer();
93
105
  context.converters = {
94
106
  html: (component) => {
@@ -107,7 +119,11 @@ describe('withApi Module', () => {
107
119
  });
108
120
 
109
121
  it('Wraps component which is object == null.', () => {
110
- const api: any = {};
122
+ const api: any = {
123
+ meta: {
124
+ name: 'foo',
125
+ },
126
+ };
111
127
  const { context } = createMockContainer();
112
128
  context.converters = {
113
129
  html: (component) => {
@@ -1,179 +1,53 @@
1
1
  import * as React from 'react';
2
- import { isfunc } from 'piral-base';
3
2
  import { __RouterContext } from 'react-router';
4
- import { PiralError, PiralLoadingIndicator, ErrorBoundary, ErrorBoundaryOptions, PortalRenderer } from '../components';
5
- import { useGlobalStateContext } from '../hooks';
6
- import { defaultRender, convertComponent, none } from '../utils';
7
- import {
8
- AnyComponent,
9
- Errors,
10
- ComponentConverters,
11
- ForeignComponent,
12
- PiletApi,
13
- BaseComponentProps,
14
- ComponentContext,
15
- GlobalStateContext,
16
- } from '../types';
17
-
18
- // this is an arbitrary start number to have 6 digits
19
- let portalIdBase = 123456;
3
+ import { ErrorBoundary, wrapComponent } from '../components';
4
+ import { defaultRender } from '../utils';
5
+ import { AnyComponent, Errors, PiletApi, BaseComponentProps, GlobalStateContext } from '../types';
20
6
 
21
7
  const DefaultWrapper: React.FC = (props) => defaultRender(props.children);
22
8
 
23
- interface ForeignComponentContainerProps<T> {
24
- $portalId: string;
25
- $component: ForeignComponent<T>;
26
- $context: ComponentContext;
27
- innerProps: T & BaseComponentProps;
28
- }
29
-
30
- class ForeignComponentContainer<T> extends React.Component<ForeignComponentContainerProps<T>> {
31
- private current?: HTMLElement;
32
- private previous?: HTMLElement;
33
- private handler = (ev: CustomEvent) => {
34
- const { innerProps } = this.props;
35
- ev.stopPropagation();
36
- innerProps.piral.renderHtmlExtension(ev.detail.target, ev.detail.props);
37
- };
38
-
39
- private setNode = (node: HTMLDivElement) => {
40
- this.current = node;
41
- };
42
-
43
- componentDidMount() {
44
- const node = this.current;
45
- const { $component, $context, innerProps } = this.props;
46
- const { mount } = $component;
47
-
48
- if (node && isfunc(mount)) {
49
- mount(node, innerProps, $context);
50
- node.addEventListener('render-html', this.handler, false);
51
- }
52
-
53
- this.previous = node;
54
- }
55
-
56
- componentDidUpdate() {
57
- const { current, previous } = this;
58
- const { $component, $context, innerProps } = this.props;
59
- const { update } = $component;
60
-
61
- if (current !== previous) {
62
- previous && this.componentWillUnmount();
63
- current && this.componentDidMount();
64
- } else if (isfunc(update)) {
65
- update(current, innerProps, $context);
66
- }
67
- }
68
-
69
- componentWillUnmount() {
70
- const node = this.previous;
71
- const { $component } = this.props;
72
- const { unmount } = $component;
73
-
74
- if (node && isfunc(unmount)) {
75
- unmount(node);
76
- node.removeEventListener('render-html', this.handler, false);
77
- }
78
-
79
- this.previous = undefined;
9
+ function getWrapper(wrappers: Record<string, React.ComponentType<any>>, wrapperType: string) {
10
+ const WrapAll = wrappers['*'];
11
+ const WrapType = wrappers[wrapperType];
12
+
13
+ if (WrapAll && WrapType) {
14
+ return (props) => (
15
+ <WrapAll {...props}>
16
+ <WrapType {...props} />
17
+ </WrapAll>
18
+ );
80
19
  }
81
20
 
82
- render() {
83
- const { $portalId } = this.props;
84
- return <div data-portal-id={$portalId} ref={this.setNode} />;
85
- }
21
+ return WrapType || WrapAll || DefaultWrapper;
86
22
  }
87
23
 
88
- function wrapReactComponent<T>(
89
- Component: React.ComponentType<T & BaseComponentProps>,
90
- stasisOptions: ErrorBoundaryOptions<T>,
91
- piral: PiletApi,
92
- Wrapper: React.ComponentType<any>,
93
- ): React.ComponentType<T> {
94
- return (props: T) => (
95
- <Wrapper {...props} piral={piral}>
96
- <ErrorBoundary {...stasisOptions} renderProps={props}>
97
- <Component {...props} piral={piral} />
24
+ function makeWrapper<TProps>(
25
+ context: GlobalStateContext,
26
+ outerProps: any,
27
+ wrapperType: string,
28
+ errorType: keyof Errors,
29
+ ): React.FC<TProps> {
30
+ const OuterWrapper = context.readState((m) => getWrapper(m.registry.wrappers, wrapperType));
31
+
32
+ return (props) => (
33
+ <OuterWrapper {...outerProps} {...props}>
34
+ <ErrorBoundary {...outerProps} {...props} errorType={errorType}>
35
+ {props.children}
98
36
  </ErrorBoundary>
99
- </Wrapper>
37
+ </OuterWrapper>
100
38
  );
101
39
  }
102
40
 
103
- function wrapForeignComponent<T>(
104
- component: ForeignComponent<T & BaseComponentProps>,
105
- stasisOptions: ErrorBoundaryOptions<T>,
106
- piral: PiletApi,
107
- Wrapper: React.ComponentType<any>,
108
- ) {
109
- return React.memo((props: T) => {
110
- const { state, readState, destroyPortal } = useGlobalStateContext();
111
- const router = React.useContext(__RouterContext);
112
- const id = React.useMemo(() => (portalIdBase++).toString(26), none);
113
- const context = React.useMemo(() => ({ router, state, readState }), [router, state]);
114
- const innerProps = React.useMemo(() => ({ ...props, piral }), [props]);
115
-
116
- React.useEffect(() => () => destroyPortal(id), none);
117
-
118
- return (
119
- <Wrapper {...innerProps}>
120
- <ErrorBoundary {...stasisOptions} renderProps={props}>
121
- <PortalRenderer id={id} />
122
- <ForeignComponentContainer innerProps={innerProps} $portalId={id} $component={component} $context={context} />
123
- </ErrorBoundary>
124
- </Wrapper>
125
- );
126
- });
127
- }
128
-
129
- function isNotExotic(component: any): component is object {
130
- return !(component as React.ExoticComponent).$$typeof;
131
- }
132
-
133
- function wrapComponent<T>(
134
- converters: ComponentConverters<T & BaseComponentProps>,
135
- component: AnyComponent<T & BaseComponentProps>,
136
- piral: PiletApi,
137
- Wrapper: React.ComponentType<any>,
138
- stasisOptions: ErrorBoundaryOptions<T>,
139
- ) {
140
- if (!component) {
141
- console.error('The given value is not a valid component.');
142
- // tslint:disable-next-line:no-null-keyword
143
- component = () => null;
144
- }
145
-
146
- if (typeof component === 'object' && isNotExotic(component)) {
147
- const result = convertComponent(converters[component.type], component);
148
- return wrapForeignComponent<T>(result, stasisOptions, piral, Wrapper);
149
- }
150
-
151
- return wrapReactComponent<T>(component, stasisOptions, piral, Wrapper);
152
- }
153
-
154
- function getWrapper(wrappers: Record<string, React.ComponentType<any>>, wrapperType: string) {
155
- return wrappers[wrapperType] || wrappers['*'] || DefaultWrapper;
156
- }
157
-
158
41
  export function withApi<TProps>(
159
42
  context: GlobalStateContext,
160
43
  component: AnyComponent<TProps & BaseComponentProps>,
161
44
  piral: PiletApi,
162
45
  errorType: keyof Errors,
163
46
  wrapperType: string = errorType,
47
+ captured = {},
164
48
  ) {
49
+ const outerProps = { ...captured, piral };
165
50
  const converters = context.converters;
166
- const Wrapper = context.readState((m) => getWrapper(m.registry.wrappers, wrapperType));
167
-
168
- return wrapComponent<TProps>(converters, component, piral, Wrapper, {
169
- onError(error) {
170
- console.error(piral, error);
171
- },
172
- renderChild(child) {
173
- return <React.Suspense fallback={<PiralLoadingIndicator />}>{child}</React.Suspense>;
174
- },
175
- renderError(error, props: any) {
176
- return <PiralError type={errorType} error={error} {...props} />;
177
- },
178
- });
51
+ const Wrapper = makeWrapper<TProps>(context, outerProps, wrapperType, errorType);
52
+ return wrapComponent(converters, component, outerProps, Wrapper);
179
53
  }
package/src/types/api.ts CHANGED
@@ -3,7 +3,7 @@ import type { RouteComponentProps } from 'react-router';
3
3
  import type { PiletApi, Pilet, PiletMetadata, EventEmitter, SinglePilet, MultiPilet } from 'piral-base';
4
4
  import type { PiletCustomApi, PiralCustomPageMeta } from './custom';
5
5
  import type { AnyComponent } from './components';
6
- import type { ExtensionSlotProps, PiralExtensionSlotMap } from './extension';
6
+ import type { ExtensionParams, ExtensionSlotProps, PiralExtensionSlotMap } from './extension';
7
7
  import type { SharedData, DataStoreOptions } from './data';
8
8
  import type { Disposable } from './utils';
9
9
 
@@ -49,7 +49,12 @@ export interface RouteBaseProps<UrlParams = any, UrlState = any>
49
49
  /**
50
50
  * The props used by a page component.
51
51
  */
52
- export interface PageComponentProps<T = any, S = any> extends RouteBaseProps<T, S> {}
52
+ export interface PageComponentProps<T = any, S = any> extends RouteBaseProps<T, S> {
53
+ /**
54
+ * The meta data registered with the page.
55
+ */
56
+ meta: PiralPageMeta;
57
+ }
53
58
 
54
59
  /**
55
60
  * The meta data registered for a page.
@@ -99,7 +104,7 @@ export interface PiletCoreApi {
99
104
  registerExtension<TName>(
100
105
  name: TName extends string ? TName : string,
101
106
  Component: AnyComponent<ExtensionComponentProps<TName>>,
102
- defaults?: TName,
107
+ defaults?: Partial<ExtensionParams<TName>>,
103
108
  ): RegistrationDisposer;
104
109
  /**
105
110
  * Unregisters a global extension component.
@@ -34,10 +34,19 @@ export interface BaseExtensionSlotProps<TName, TParams> {
34
34
  name: TName;
35
35
  }
36
36
 
37
+ /**
38
+ * Gives the extension params shape for the given extension slot name.
39
+ */
40
+ export type ExtensionParams<TName> = TName extends keyof PiralExtensionSlotMap
41
+ ? PiralExtensionSlotMap[TName]
42
+ : TName extends string
43
+ ? any
44
+ : TName;
45
+
37
46
  /**
38
47
  * The props for defining an extension slot.
39
48
  */
40
- export type ExtensionSlotProps<K = string> = BaseExtensionSlotProps<
41
- K extends string ? K : string,
42
- K extends keyof PiralExtensionSlotMap ? PiralExtensionSlotMap[K] : K extends string ? any : K
49
+ export type ExtensionSlotProps<TName = string> = BaseExtensionSlotProps<
50
+ TName extends string ? TName : string,
51
+ ExtensionParams<TName>
43
52
  >;