piral-core 0.14.8-beta.3500 → 0.14.8-beta.3511

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 (113) hide show
  1. package/esm/Piral.d.ts +2 -2
  2. package/esm/Piral.js +3 -1
  3. package/esm/Piral.js.map +1 -1
  4. package/esm/RootListener.d.ts +2 -0
  5. package/esm/RootListener.js +23 -0
  6. package/esm/RootListener.js.map +1 -0
  7. package/esm/components/ExtensionSlot.js +2 -2
  8. package/esm/components/ExtensionSlot.js.map +1 -1
  9. package/esm/components/Mediator.js +11 -8
  10. package/esm/components/Mediator.js.map +1 -1
  11. package/esm/components/ResponsiveLayout.js +3 -3
  12. package/esm/components/ResponsiveLayout.js.map +1 -1
  13. package/esm/components/SetComponent.js +2 -2
  14. package/esm/components/SetComponent.js.map +1 -1
  15. package/esm/components/SetError.js +2 -2
  16. package/esm/components/SetError.js.map +1 -1
  17. package/esm/components/SetProvider.js +2 -2
  18. package/esm/components/SetProvider.js.map +1 -1
  19. package/esm/components/SetRedirect.js +2 -2
  20. package/esm/components/SetRedirect.js.map +1 -1
  21. package/esm/components/SetRoute.js +2 -2
  22. package/esm/components/SetRoute.js.map +1 -1
  23. package/esm/createInstance.d.ts +2 -2
  24. package/esm/createInstance.js +3 -1
  25. package/esm/createInstance.js.map +1 -1
  26. package/esm/modules/api.d.ts +3 -4
  27. package/esm/modules/api.js +1 -106
  28. package/esm/modules/api.js.map +1 -1
  29. package/esm/modules/core.d.ts +3 -0
  30. package/esm/modules/core.js +48 -0
  31. package/esm/modules/core.js.map +1 -0
  32. package/esm/modules/element.d.ts +5 -0
  33. package/esm/modules/element.js +83 -0
  34. package/esm/modules/element.js.map +1 -0
  35. package/esm/modules/index.d.ts +1 -0
  36. package/esm/modules/index.js +1 -0
  37. package/esm/modules/index.js.map +1 -1
  38. package/esm/state/withApi.js +2 -3
  39. package/esm/state/withApi.js.map +1 -1
  40. package/esm/types/extension.d.ts +7 -3
  41. package/esm/types/instance.d.ts +18 -3
  42. package/esm/utils/extension.d.ts +13 -0
  43. package/esm/utils/extension.js +32 -0
  44. package/esm/utils/extension.js.map +1 -1
  45. package/lib/Piral.d.ts +2 -2
  46. package/lib/Piral.js +3 -1
  47. package/lib/Piral.js.map +1 -1
  48. package/lib/RootListener.d.ts +2 -0
  49. package/lib/RootListener.js +27 -0
  50. package/lib/RootListener.js.map +1 -0
  51. package/lib/components/ExtensionSlot.js +2 -2
  52. package/lib/components/ExtensionSlot.js.map +1 -1
  53. package/lib/components/Mediator.js +10 -7
  54. package/lib/components/Mediator.js.map +1 -1
  55. package/lib/components/ResponsiveLayout.js +2 -2
  56. package/lib/components/ResponsiveLayout.js.map +1 -1
  57. package/lib/components/SetComponent.js +1 -1
  58. package/lib/components/SetComponent.js.map +1 -1
  59. package/lib/components/SetError.js +1 -1
  60. package/lib/components/SetError.js.map +1 -1
  61. package/lib/components/SetProvider.js +1 -1
  62. package/lib/components/SetProvider.js.map +1 -1
  63. package/lib/components/SetRedirect.js +1 -1
  64. package/lib/components/SetRedirect.js.map +1 -1
  65. package/lib/components/SetRoute.js +1 -1
  66. package/lib/components/SetRoute.js.map +1 -1
  67. package/lib/createInstance.d.ts +2 -2
  68. package/lib/createInstance.js +3 -1
  69. package/lib/createInstance.js.map +1 -1
  70. package/lib/modules/api.d.ts +3 -4
  71. package/lib/modules/api.js +3 -109
  72. package/lib/modules/api.js.map +1 -1
  73. package/lib/modules/core.d.ts +3 -0
  74. package/lib/modules/core.js +52 -0
  75. package/lib/modules/core.js.map +1 -0
  76. package/lib/modules/element.d.ts +5 -0
  77. package/lib/modules/element.js +87 -0
  78. package/lib/modules/element.js.map +1 -0
  79. package/lib/modules/index.d.ts +1 -0
  80. package/lib/modules/index.js +1 -0
  81. package/lib/modules/index.js.map +1 -1
  82. package/lib/state/withApi.js +1 -2
  83. package/lib/state/withApi.js.map +1 -1
  84. package/lib/types/extension.d.ts +7 -3
  85. package/lib/types/instance.d.ts +18 -3
  86. package/lib/utils/extension.d.ts +13 -0
  87. package/lib/utils/extension.js +34 -1
  88. package/lib/utils/extension.js.map +1 -1
  89. package/package.json +4 -4
  90. package/src/Piral.tsx +5 -3
  91. package/src/RootListener.tsx +26 -0
  92. package/src/actions/app.ts +1 -1
  93. package/src/components/ExtensionSlot.tsx +2 -1
  94. package/src/components/Mediator.test.tsx +4 -3
  95. package/src/components/Mediator.tsx +15 -8
  96. package/src/components/ResponsiveLayout.test.tsx +15 -5
  97. package/src/components/ResponsiveLayout.tsx +3 -3
  98. package/src/components/SetComponent.tsx +2 -2
  99. package/src/components/SetError.tsx +2 -2
  100. package/src/components/SetProvider.tsx +2 -2
  101. package/src/components/SetRedirect.tsx +2 -2
  102. package/src/components/SetRoute.tsx +2 -2
  103. package/src/createInstance.tsx +5 -2
  104. package/src/modules/api.test.ts +15 -15
  105. package/src/modules/api.ts +3 -125
  106. package/src/modules/core.test.ts +148 -0
  107. package/src/modules/core.ts +50 -0
  108. package/src/modules/element.ts +103 -0
  109. package/src/modules/index.ts +1 -0
  110. package/src/state/withApi.tsx +2 -3
  111. package/src/types/extension.ts +7 -2
  112. package/src/types/instance.ts +19 -3
  113. package/src/utils/extension.tsx +41 -0
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { Redirect } from 'react-router';
3
- import { useAction, useSetter } from '../hooks';
3
+ import { useGlobalStateContext, useSetter } from '../hooks';
4
4
 
5
5
  /**
6
6
  * The props for the SetRedirect component.
@@ -20,7 +20,7 @@ export interface SetRedirectProps {
20
20
  * The component capable of setting a global redirect route at mounting.
21
21
  */
22
22
  export function SetRedirect({ from, to }: SetRedirectProps): React.ReactElement {
23
- const setRoute = useAction('setRoute');
23
+ const { setRoute } = useGlobalStateContext();
24
24
  useSetter(() => setRoute(from, () => <Redirect to={to} />));
25
25
  // tslint:disable-next-line:no-null-keyword
26
26
  return null;
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { RouteComponentProps } from 'react-router';
3
- import { useAction, useSetter } from '../hooks';
3
+ import { useGlobalStateContext, useSetter } from '../hooks';
4
4
 
5
5
  /**
6
6
  * The props for the SetRoute component.
@@ -20,7 +20,7 @@ export interface SetRouteProps<T = {}> {
20
20
  * The component capable of setting a global route at mounting.
21
21
  */
22
22
  export function SetRoute<T = {}>({ path, component }: SetRouteProps<T>): React.ReactElement {
23
- const setRoute = useAction('setRoute');
23
+ const { setRoute } = useGlobalStateContext();
24
24
  useSetter(() => component && setRoute(path, component));
25
25
  // tslint:disable-next-line:no-null-keyword
26
26
  return null;
@@ -3,7 +3,8 @@ import { blazingStrategy, standardStrategy, createListener, isfunc } from 'piral
3
3
  import { defaultApiFactory, defaultDependencySelector, defaultModuleRequester } from './modules';
4
4
  import { createGlobalState, createActions, includeActions } from './state';
5
5
  import { createPiletOptions } from './helpers';
6
- import type { PiralConfiguration, PiralInstance } from './types';
6
+ import { generateId } from './utils';
7
+ import type { PiralInstanceOptions, PiralInstance } from './types';
7
8
 
8
9
  /**
9
10
  * Creates a new PiralInstance component, which can be used for
@@ -25,8 +26,9 @@ const app = (
25
26
  render(app, document.querySelector('#app'));
26
27
  ```
27
28
  */
28
- export function createInstance(config: PiralConfiguration = {}): PiralInstance {
29
+ export function createInstance(config: PiralInstanceOptions = {}): PiralInstance {
29
30
  const {
31
+ id = generateId(),
30
32
  state,
31
33
  actions,
32
34
  availablePilets = [],
@@ -71,6 +73,7 @@ export function createInstance(config: PiralConfiguration = {}): PiralInstance {
71
73
  context.options = options;
72
74
 
73
75
  return __assign(events, {
76
+ id,
74
77
  createApi,
75
78
  context,
76
79
  root,
@@ -1,5 +1,5 @@
1
1
  import { createElement, FC } from 'react';
2
- import { createCoreApi } from './api';
2
+ import { defaultApiFactory } from './api';
3
3
 
4
4
  jest.mock('../hooks');
5
5
 
@@ -21,21 +21,21 @@ function createMockContainer() {
21
21
  off: jest.fn(),
22
22
  emit: jest.fn(),
23
23
  converters: {},
24
+ apis: {},
24
25
  readState() {
25
26
  return undefined;
26
27
  },
27
28
  } as any,
28
- api: {} as any,
29
29
  };
30
30
  }
31
31
 
32
- function createApi(container) {
33
- Object.assign(container.api, createCoreApi(container.context)(container.api, moduleMetadata));
34
- return container.api;
32
+ function createApi(container, apis = []) {
33
+ const api = defaultApiFactory(container.context, apis);
34
+ return api(moduleMetadata);
35
35
  }
36
36
 
37
37
  describe('API Module', () => {
38
- it('createCoreApi can register and unregister a page', () => {
38
+ it('defaultApiFactory can register and unregister a page', () => {
39
39
  const container = createMockContainer();
40
40
  container.context.registerPage = jest.fn();
41
41
  container.context.unregisterPage = jest.fn();
@@ -48,7 +48,7 @@ describe('API Module', () => {
48
48
  expect(container.context.unregisterPage.mock.calls[0][0]).toBe(container.context.registerPage.mock.calls[0][0]);
49
49
  });
50
50
 
51
- it('createCoreApi can dispose a registered page', () => {
51
+ it('defaultApiFactory can dispose a registered page', () => {
52
52
  const container = createMockContainer();
53
53
  container.context.registerPage = jest.fn();
54
54
  container.context.unregisterPage = jest.fn();
@@ -61,7 +61,7 @@ describe('API Module', () => {
61
61
  expect(container.context.unregisterPage.mock.calls[0][0]).toBe(container.context.registerPage.mock.calls[0][0]);
62
62
  });
63
63
 
64
- it('createCoreApi can register and unregister an extension', () => {
64
+ it('defaultApiFactory can register and unregister an extension', () => {
65
65
  const container = createMockContainer();
66
66
  container.context.registerExtension = jest.fn();
67
67
  container.context.unregisterExtension = jest.fn();
@@ -76,7 +76,7 @@ describe('API Module', () => {
76
76
  );
77
77
  });
78
78
 
79
- it('createCoreApi can dispose an registered extension', () => {
79
+ it('defaultApiFactory can dispose an registered extension', () => {
80
80
  const container = createMockContainer();
81
81
  container.context.registerExtension = jest.fn();
82
82
  container.context.unregisterExtension = jest.fn();
@@ -91,7 +91,7 @@ describe('API Module', () => {
91
91
  );
92
92
  });
93
93
 
94
- it('createCoreApi read data by its name', () => {
94
+ it('defaultApiFactory read data by its name', () => {
95
95
  const container = createMockContainer();
96
96
  container.context.readDataValue = jest.fn((name) => name);
97
97
  const api = createApi(container);
@@ -100,7 +100,7 @@ describe('API Module', () => {
100
100
  expect(container.context.readDataValue).toHaveBeenCalled();
101
101
  });
102
102
 
103
- it('createCoreApi write data without options shall pass, but memory should not emit events', () => {
103
+ it('defaultApiFactory write data without options shall pass, but memory should not emit events', () => {
104
104
  const container = createMockContainer();
105
105
  container.context.tryWriteDataItem = jest.fn(() => true);
106
106
  const api = createApi(container);
@@ -109,7 +109,7 @@ describe('API Module', () => {
109
109
  expect(container.context.emit).not.toHaveBeenCalled();
110
110
  });
111
111
 
112
- it('createCoreApi write data with empty options shall pass, but memory should not emit events', () => {
112
+ it('defaultApiFactory write data with empty options shall pass, but memory should not emit events', () => {
113
113
  const container = createMockContainer();
114
114
  container.context.tryWriteDataItem = jest.fn(() => true);
115
115
  const api = createApi(container);
@@ -118,7 +118,7 @@ describe('API Module', () => {
118
118
  expect(container.context.emit).not.toHaveBeenCalled();
119
119
  });
120
120
 
121
- it('createCoreApi write data by the simple option should not pass, never emitting events', () => {
121
+ it('defaultApiFactory write data by the simple option should not pass, never emitting events', () => {
122
122
  const container = createMockContainer();
123
123
  container.context.tryWriteDataItem = jest.fn(() => false);
124
124
  const api = createApi(container);
@@ -127,7 +127,7 @@ describe('API Module', () => {
127
127
  expect(container.context.emit).not.toHaveBeenCalled();
128
128
  });
129
129
 
130
- it('createCoreApi write data by the simple option shall pass with remote', () => {
130
+ it('defaultApiFactory write data by the simple option shall pass with remote', () => {
131
131
  const container = createMockContainer();
132
132
  container.context.tryWriteDataItem = jest.fn(() => true);
133
133
  const api = createApi(container);
@@ -135,7 +135,7 @@ describe('API Module', () => {
135
135
  expect(container.context.tryWriteDataItem).toHaveBeenCalled();
136
136
  });
137
137
 
138
- it('createCoreApi write data by the object options shall pass with remote', () => {
138
+ it('defaultApiFactory write data by the object options shall pass with remote', () => {
139
139
  const container = createMockContainer();
140
140
  container.context.tryWriteDataItem = jest.fn(() => true);
141
141
  const api = createApi(container);
@@ -1,129 +1,7 @@
1
- import { isfunc, PiletApiCreator, PiletApiExtender, initializeApi, mergeApis } from 'piral-base';
1
+ import { isfunc, PiletApiCreator, initializeApi, mergeApis } from 'piral-base';
2
2
  import { __assign } from 'tslib';
3
- import { withApi } from '../state';
4
- import { ExtensionSlot } from '../components';
5
- import { createDataOptions, getDataExpiration, renderInDom, tryParseJson, changeDomPortal, noop } from '../utils';
6
- import { Disposable, GlobalStateContext, PiletCoreApi, PiralPlugin } from '../types';
7
-
8
- interface Updatable {
9
- (newProps: any): void;
10
- }
11
-
12
- if (typeof window !== 'undefined' && 'customElements' in window) {
13
- class PiralExtension extends HTMLElement {
14
- dispose: Disposable = noop;
15
- update: Updatable = noop;
16
-
17
- getProps() {
18
- const name = this.getAttribute('name');
19
- const params = tryParseJson(this.getAttribute('params'));
20
- return { name, params };
21
- }
22
-
23
- connectedCallback() {
24
- if (this.isConnected) {
25
- this.dispatchEvent(
26
- new CustomEvent('render-html', {
27
- bubbles: true,
28
- detail: {
29
- target: this,
30
- props: this.getProps(),
31
- },
32
- }),
33
- );
34
- }
35
- }
36
-
37
- disconnectedCallback() {
38
- this.dispose();
39
- this.dispose = noop;
40
- this.update = noop;
41
- }
42
-
43
- attributeChangedCallback() {
44
- this.update(this.getProps());
45
- }
46
-
47
- static get observedAttributes() {
48
- return ['name', 'params'];
49
- }
50
- }
51
-
52
- customElements.define('piral-extension', PiralExtension);
53
- }
54
-
55
- function render(context: GlobalStateContext, element: HTMLElement | ShadowRoot, props: any): [Disposable, Updatable] {
56
- let [id, portal] = renderInDom(context, element, ExtensionSlot, props);
57
- const evName = 'extension-props-changed';
58
- const handler = (ev: CustomEvent) => update(ev.detail);
59
- const dispose: Disposable = () => {
60
- context.hidePortal(id, portal);
61
- element.removeEventListener(evName, handler);
62
- };
63
- const update: Updatable = (newProps) => {
64
- [id, portal] = changeDomPortal(id, portal, context, element, ExtensionSlot, newProps);
65
- };
66
- element.addEventListener(evName, handler);
67
- return [dispose, update];
68
- }
69
-
70
- export function createCoreApi(context: GlobalStateContext): PiletApiExtender<PiletCoreApi> {
71
- if (typeof document !== 'undefined') {
72
- document.body.addEventListener(
73
- 'render-html',
74
- (ev: CustomEvent) => {
75
- ev.stopPropagation();
76
- const container = ev.detail.target;
77
- const [dispose, update] = render(context, container, ev.detail.props);
78
- container.dispose = dispose;
79
- container.update = update;
80
- },
81
- false,
82
- );
83
- }
84
-
85
- return (api, target) => {
86
- const pilet = target.name;
87
- return {
88
- getData(name) {
89
- return context.readDataValue(name);
90
- },
91
- setData(name, value, options) {
92
- const { target = 'memory', expires } = createDataOptions(options);
93
- const expiration = getDataExpiration(expires);
94
- return context.tryWriteDataItem(name, value, pilet, target, expiration);
95
- },
96
- registerPage(route, arg, meta) {
97
- context.registerPage(route, {
98
- pilet,
99
- meta,
100
- component: withApi(context, arg, api, 'page'),
101
- });
102
- return () => api.unregisterPage(route);
103
- },
104
- unregisterPage(route) {
105
- context.unregisterPage(route);
106
- },
107
- registerExtension(name, arg, defaults) {
108
- context.registerExtension(name as string, {
109
- pilet,
110
- component: withApi(context, arg, api, 'extension'),
111
- reference: arg,
112
- defaults,
113
- });
114
- return () => api.unregisterExtension(name, arg);
115
- },
116
- unregisterExtension(name, arg) {
117
- context.unregisterExtension(name as string, arg);
118
- },
119
- renderHtmlExtension(element, props) {
120
- const [dispose] = render(context, element, props);
121
- return dispose;
122
- },
123
- Extension: ExtensionSlot,
124
- };
125
- };
126
- }
3
+ import { createCoreApi } from './core';
4
+ import { GlobalStateContext, PiralPlugin } from '../types';
127
5
 
128
6
  export function createExtenders(context: GlobalStateContext, apis: Array<PiralPlugin>) {
129
7
  const creators: Array<PiralPlugin> = [createCoreApi, ...apis.filter(isfunc)];
@@ -0,0 +1,148 @@
1
+ import { createElement, FC } from 'react';
2
+ import { createCoreApi } from './core';
3
+
4
+ jest.mock('../hooks');
5
+
6
+ const StubComponent: FC = (props) => createElement('div', props);
7
+ StubComponent.displayName = 'StubComponent';
8
+
9
+ const moduleMetadata = {
10
+ name: 'my-module',
11
+ version: '1.0.0',
12
+ link: undefined,
13
+ custom: undefined,
14
+ hash: '123',
15
+ };
16
+
17
+ function createMockContainer() {
18
+ return {
19
+ context: {
20
+ on: jest.fn(),
21
+ off: jest.fn(),
22
+ emit: jest.fn(),
23
+ converters: {},
24
+ readState() {
25
+ return undefined;
26
+ },
27
+ } as any,
28
+ api: {} as any,
29
+ };
30
+ }
31
+
32
+ function createApi(container) {
33
+ Object.assign(container.api, createCoreApi(container.context)(container.api, moduleMetadata));
34
+ return container.api;
35
+ }
36
+
37
+ describe('Core API Module', () => {
38
+ it('createCoreApi can register and unregister a page', () => {
39
+ const container = createMockContainer();
40
+ container.context.registerPage = jest.fn();
41
+ container.context.unregisterPage = jest.fn();
42
+ const api = createApi(container);
43
+ api.registerPage('/route', StubComponent);
44
+ expect(container.context.registerPage).toHaveBeenCalledTimes(1);
45
+ expect(container.context.unregisterPage).toHaveBeenCalledTimes(0);
46
+ api.unregisterPage('/route');
47
+ expect(container.context.unregisterPage).toHaveBeenCalledTimes(1);
48
+ expect(container.context.unregisterPage.mock.calls[0][0]).toBe(container.context.registerPage.mock.calls[0][0]);
49
+ });
50
+
51
+ it('createCoreApi can dispose a registered page', () => {
52
+ const container = createMockContainer();
53
+ container.context.registerPage = jest.fn();
54
+ container.context.unregisterPage = jest.fn();
55
+ const api = createApi(container);
56
+ const dispose = api.registerPage('/route', StubComponent);
57
+ expect(container.context.registerPage).toHaveBeenCalledTimes(1);
58
+ expect(container.context.unregisterPage).toHaveBeenCalledTimes(0);
59
+ dispose();
60
+ expect(container.context.unregisterPage).toHaveBeenCalledTimes(1);
61
+ expect(container.context.unregisterPage.mock.calls[0][0]).toBe(container.context.registerPage.mock.calls[0][0]);
62
+ });
63
+
64
+ it('createCoreApi can register and unregister an extension', () => {
65
+ const container = createMockContainer();
66
+ container.context.registerExtension = jest.fn();
67
+ container.context.unregisterExtension = jest.fn();
68
+ const api = createApi(container);
69
+ api.registerExtension('ext', StubComponent);
70
+ expect(container.context.registerExtension).toHaveBeenCalledTimes(1);
71
+ expect(container.context.unregisterExtension).toHaveBeenCalledTimes(0);
72
+ api.unregisterExtension('ext', StubComponent);
73
+ expect(container.context.unregisterExtension).toHaveBeenCalledTimes(1);
74
+ expect(container.context.unregisterExtension.mock.calls[0][0]).toBe(
75
+ container.context.registerExtension.mock.calls[0][0],
76
+ );
77
+ });
78
+
79
+ it('createCoreApi can dispose an registered extension', () => {
80
+ const container = createMockContainer();
81
+ container.context.registerExtension = jest.fn();
82
+ container.context.unregisterExtension = jest.fn();
83
+ const api = createApi(container);
84
+ const dispose = api.registerExtension('ext', StubComponent);
85
+ expect(container.context.registerExtension).toHaveBeenCalledTimes(1);
86
+ expect(container.context.unregisterExtension).toHaveBeenCalledTimes(0);
87
+ dispose();
88
+ expect(container.context.unregisterExtension).toHaveBeenCalledTimes(1);
89
+ expect(container.context.unregisterExtension.mock.calls[0][0]).toBe(
90
+ container.context.registerExtension.mock.calls[0][0],
91
+ );
92
+ });
93
+
94
+ it('createCoreApi read data by its name', () => {
95
+ const container = createMockContainer();
96
+ container.context.readDataValue = jest.fn((name) => name);
97
+ const api = createApi(container);
98
+ const result = api.getData('foo');
99
+ expect(result).toBe('foo');
100
+ expect(container.context.readDataValue).toHaveBeenCalled();
101
+ });
102
+
103
+ it('createCoreApi write data without options shall pass, but memory should not emit events', () => {
104
+ const container = createMockContainer();
105
+ container.context.tryWriteDataItem = jest.fn(() => true);
106
+ const api = createApi(container);
107
+ api.setData('foo', 5);
108
+ expect(container.context.tryWriteDataItem).toHaveBeenCalled();
109
+ expect(container.context.emit).not.toHaveBeenCalled();
110
+ });
111
+
112
+ it('createCoreApi write data with empty options shall pass, but memory should not emit events', () => {
113
+ const container = createMockContainer();
114
+ container.context.tryWriteDataItem = jest.fn(() => true);
115
+ const api = createApi(container);
116
+ api.setData('foo', 5, {});
117
+ expect(container.context.tryWriteDataItem).toHaveBeenCalled();
118
+ expect(container.context.emit).not.toHaveBeenCalled();
119
+ });
120
+
121
+ it('createCoreApi write data by the simple option should not pass, never emitting events', () => {
122
+ const container = createMockContainer();
123
+ container.context.tryWriteDataItem = jest.fn(() => false);
124
+ const api = createApi(container);
125
+ api.setData('foo', 5, 'remote');
126
+ expect(container.context.tryWriteDataItem).toHaveBeenCalled();
127
+ expect(container.context.emit).not.toHaveBeenCalled();
128
+ });
129
+
130
+ it('createCoreApi write data by the simple option shall pass with remote', () => {
131
+ const container = createMockContainer();
132
+ container.context.tryWriteDataItem = jest.fn(() => true);
133
+ const api = createApi(container);
134
+ api.setData('foo', 5, 'remote');
135
+ expect(container.context.tryWriteDataItem).toHaveBeenCalled();
136
+ });
137
+
138
+ it('createCoreApi write data by the object options shall pass with remote', () => {
139
+ const container = createMockContainer();
140
+ container.context.tryWriteDataItem = jest.fn(() => true);
141
+ const api = createApi(container);
142
+ api.setData('foo', 15, {
143
+ expires: 10,
144
+ target: 'local',
145
+ });
146
+ expect(container.context.tryWriteDataItem).toHaveBeenCalled();
147
+ });
148
+ });
@@ -0,0 +1,50 @@
1
+ import { PiletApiExtender } from 'piral-base';
2
+ import { renderElement } from './element';
3
+ import { withApi } from '../state';
4
+ import { ExtensionSlot } from '../components';
5
+ import { createDataOptions, getDataExpiration } from '../utils';
6
+ import { GlobalStateContext, PiletCoreApi } from '../types';
7
+
8
+ export function createCoreApi(context: GlobalStateContext): PiletApiExtender<PiletCoreApi> {
9
+ return (api, meta) => {
10
+ const pilet = meta.name;
11
+ return {
12
+ getData(name) {
13
+ return context.readDataValue(name);
14
+ },
15
+ setData(name, value, options) {
16
+ const { target = 'memory', expires } = createDataOptions(options);
17
+ const expiration = getDataExpiration(expires);
18
+ return context.tryWriteDataItem(name, value, pilet, target, expiration);
19
+ },
20
+ registerPage(route, arg, meta) {
21
+ context.registerPage(route, {
22
+ pilet,
23
+ meta,
24
+ component: withApi(context, arg, api, 'page'),
25
+ });
26
+ return () => api.unregisterPage(route);
27
+ },
28
+ unregisterPage(route) {
29
+ context.unregisterPage(route);
30
+ },
31
+ registerExtension(name, arg, defaults) {
32
+ context.registerExtension(name as string, {
33
+ pilet,
34
+ component: withApi(context, arg, api, 'extension'),
35
+ reference: arg,
36
+ defaults,
37
+ });
38
+ return () => api.unregisterExtension(name, arg);
39
+ },
40
+ unregisterExtension(name, arg) {
41
+ context.unregisterExtension(name as string, arg);
42
+ },
43
+ renderHtmlExtension(element, props) {
44
+ const [dispose] = renderElement(context, element, props);
45
+ return dispose;
46
+ },
47
+ Extension: ExtensionSlot,
48
+ };
49
+ };
50
+ }
@@ -0,0 +1,103 @@
1
+ import { ExtensionSlot } from '../components';
2
+ import { tryParseJson, noop, reactifyContent, renderInDom, changeDomPortal } from '../utils';
3
+ import { Disposable, GlobalStateContext } from '../types';
4
+
5
+ export interface Updatable {
6
+ (newProps: any): void;
7
+ }
8
+
9
+ if (typeof window !== 'undefined' && 'customElements' in window) {
10
+ class PiralExtension extends HTMLElement {
11
+ dispose: Disposable = noop;
12
+ update: Updatable = noop;
13
+ props = {
14
+ name: this.getAttribute('name'),
15
+ params: tryParseJson(this.getAttribute('params')),
16
+ empty: undefined,
17
+ children: reactifyContent(this.childNodes),
18
+ };
19
+
20
+ get params() {
21
+ return this.props.params;
22
+ }
23
+
24
+ set params(value) {
25
+ this.props.params = value;
26
+ this.update(this.props);
27
+ }
28
+
29
+ get name() {
30
+ return this.props.name;
31
+ }
32
+
33
+ set name(value) {
34
+ this.props.name = value;
35
+ this.update(this.props);
36
+ }
37
+
38
+ get empty() {
39
+ return this.props.empty;
40
+ }
41
+
42
+ set empty(value) {
43
+ this.props.empty = value;
44
+ this.update(this.props);
45
+ }
46
+
47
+ connectedCallback() {
48
+ if (this.isConnected) {
49
+ this.dispatchEvent(
50
+ new CustomEvent('render-html', {
51
+ bubbles: true,
52
+ detail: {
53
+ target: this,
54
+ props: this.props,
55
+ },
56
+ }),
57
+ );
58
+ }
59
+ }
60
+
61
+ disconnectedCallback() {
62
+ this.dispose();
63
+ this.dispose = noop;
64
+ this.update = noop;
65
+ }
66
+
67
+ attributeChangedCallback(name: string, _: any, newValue: any) {
68
+ switch (name) {
69
+ case 'name':
70
+ this.name = newValue;
71
+ break;
72
+ case 'params':
73
+ this.params = tryParseJson(newValue);
74
+ break;
75
+ }
76
+ }
77
+
78
+ static get observedAttributes() {
79
+ return ['name', 'params'];
80
+ }
81
+ }
82
+
83
+ customElements.define('piral-extension', PiralExtension);
84
+ }
85
+
86
+ export function renderElement(
87
+ context: GlobalStateContext,
88
+ element: HTMLElement | ShadowRoot,
89
+ props: any,
90
+ ): [Disposable, Updatable] {
91
+ let [id, portal] = renderInDom(context, element, ExtensionSlot, props);
92
+ const evName = 'extension-props-changed';
93
+ const handler = (ev: CustomEvent) => update(ev.detail);
94
+ const dispose: Disposable = () => {
95
+ context.hidePortal(id, portal);
96
+ element.removeEventListener(evName, handler);
97
+ };
98
+ const update: Updatable = (newProps) => {
99
+ [id, portal] = changeDomPortal(id, portal, context, element, ExtensionSlot, newProps);
100
+ };
101
+ element.addEventListener(evName, handler);
102
+ return [dispose, update];
103
+ }
@@ -1,2 +1,3 @@
1
1
  export * from './api';
2
2
  export * from './dependencies';
3
+ export * from './element';
@@ -2,7 +2,7 @@ import * as React from 'react';
2
2
  import { isfunc } from 'piral-base';
3
3
  import { __RouterContext } from 'react-router';
4
4
  import { PiralError, PiralLoadingIndicator, ErrorBoundary, ErrorBoundaryOptions, PortalRenderer } from '../components';
5
- import { useActions, useGlobalStateContext } from '../hooks';
5
+ import { useGlobalStateContext } from '../hooks';
6
6
  import { defaultRender, convertComponent, none } from '../utils';
7
7
  import {
8
8
  AnyComponent,
@@ -107,8 +107,7 @@ function wrapForeignComponent<T>(
107
107
  Wrapper: React.ComponentType<any>,
108
108
  ) {
109
109
  return React.memo((props: T) => {
110
- const { destroyPortal } = useActions();
111
- const { state, readState } = useGlobalStateContext();
110
+ const { state, readState, destroyPortal } = useGlobalStateContext();
112
111
  const router = React.useContext(__RouterContext);
113
112
  const id = React.useMemo(() => (portalIdBase++).toString(26), none);
114
113
  const context = React.useMemo(() => ({ router, state, readState }), [router, state]);
@@ -1,3 +1,4 @@
1
+ import type { ReactNode, ReactElement } from 'react';
1
2
  import type { PiralCustomExtensionSlotMap } from './custom';
2
3
 
3
4
  /**
@@ -9,16 +10,20 @@ export interface PiralExtensionSlotMap extends PiralCustomExtensionSlotMap {}
9
10
  * The basic props for defining an extension slot.
10
11
  */
11
12
  export interface BaseExtensionSlotProps<TName, TParams> {
13
+ /**
14
+ * The children to transport, if any.
15
+ */
16
+ children?: ReactNode;
12
17
  /**
13
18
  * Defines what should be rendered when no components are available
14
19
  * for the specified extension.
15
20
  */
16
- empty?(): React.ReactNode;
21
+ empty?(): ReactNode;
17
22
  /**
18
23
  * Defines how the provided nodes should be rendered.
19
24
  * @param nodes The rendered extension nodes.
20
25
  */
21
- render?(nodes: Array<React.ReactNode>): React.ReactElement<any, any> | null;
26
+ render?(nodes: Array<ReactNode>): ReactElement<any, any> | null;
22
27
  /**
23
28
  * The custom parameters for the given extension.
24
29
  */