piral-core 0.14.8 → 0.14.10-beta.3615
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/esm/components/ErrorBoundary.d.ts +12 -33
- package/esm/components/ErrorBoundary.js +12 -14
- package/esm/components/ErrorBoundary.js.map +1 -1
- package/esm/components/ForeignComponentContainer.d.ts +19 -0
- package/esm/components/ForeignComponentContainer.js +52 -0
- package/esm/components/ForeignComponentContainer.js.map +1 -0
- package/esm/components/index.d.ts +1 -0
- package/esm/components/index.js +1 -0
- package/esm/components/index.js.map +1 -1
- package/esm/components/wrapComponent.d.ts +7 -0
- package/esm/components/wrapComponent.js +42 -0
- package/esm/components/wrapComponent.js.map +1 -0
- package/esm/modules/core.js +3 -2
- package/esm/modules/core.js.map +1 -1
- package/esm/state/withApi.d.ts +1 -1
- package/esm/state/withApi.js +17 -103
- package/esm/state/withApi.js.map +1 -1
- package/esm/types/api.d.ts +2 -2
- package/esm/types/extension.d.ts +5 -1
- package/lib/components/ErrorBoundary.d.ts +12 -33
- package/lib/components/ErrorBoundary.js +12 -14
- package/lib/components/ErrorBoundary.js.map +1 -1
- package/lib/components/ForeignComponentContainer.d.ts +19 -0
- package/lib/components/ForeignComponentContainer.js +56 -0
- package/lib/components/ForeignComponentContainer.js.map +1 -0
- package/lib/components/index.d.ts +1 -0
- package/lib/components/index.js +1 -0
- package/lib/components/index.js.map +1 -1
- package/lib/components/wrapComponent.d.ts +7 -0
- package/lib/components/wrapComponent.js +46 -0
- package/lib/components/wrapComponent.js.map +1 -0
- package/lib/modules/core.js +3 -2
- package/lib/modules/core.js.map +1 -1
- package/lib/state/withApi.d.ts +1 -1
- package/lib/state/withApi.js +15 -101
- package/lib/state/withApi.js.map +1 -1
- package/lib/types/api.d.ts +2 -2
- package/lib/types/extension.d.ts +5 -1
- package/package.json +4 -4
- package/src/components/ErrorBoundary.tsx +19 -51
- package/src/components/ForeignComponentContainer.tsx +69 -0
- package/src/components/index.ts +1 -0
- package/src/components/wrapComponent.tsx +74 -0
- package/src/modules/core.ts +3 -2
- package/src/state/withApi.test.tsx +20 -4
- package/src/state/withApi.tsx +31 -157
- package/src/types/api.ts +2 -2
- 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
|
+
}
|
package/src/modules/core.ts
CHANGED
|
@@ -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
|
|
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) => {
|
package/src/state/withApi.tsx
CHANGED
|
@@ -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 {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
): React.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
</
|
|
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
|
|
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
|
|
|
@@ -99,7 +99,7 @@ export interface PiletCoreApi {
|
|
|
99
99
|
registerExtension<TName>(
|
|
100
100
|
name: TName extends string ? TName : string,
|
|
101
101
|
Component: AnyComponent<ExtensionComponentProps<TName>>,
|
|
102
|
-
defaults?: TName
|
|
102
|
+
defaults?: Partial<ExtensionParams<TName>>,
|
|
103
103
|
): RegistrationDisposer;
|
|
104
104
|
/**
|
|
105
105
|
* Unregisters a global extension component.
|
package/src/types/extension.ts
CHANGED
|
@@ -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<
|
|
41
|
-
|
|
42
|
-
|
|
49
|
+
export type ExtensionSlotProps<TName = string> = BaseExtensionSlotProps<
|
|
50
|
+
TName extends string ? TName : string,
|
|
51
|
+
ExtensionParams<TName>
|
|
43
52
|
>;
|