@openmrs/esm-extensions 6.3.1-pre.2965 → 6.3.1-pre.2997

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/.swcrc ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "https://swc.rs/schema.json",
3
+ "exclude": [".*\\.test\\..*", "setup-tests\\..*"],
4
+ "module": {
5
+ "type": "es6",
6
+ "resolveFully": true
7
+ },
8
+ "jsc": {
9
+ "parser": {
10
+ "syntax": "typescript",
11
+ "tsx": false
12
+ },
13
+ "target": "es2020",
14
+ "baseUrl": "src"
15
+ }
16
+ }
@@ -1,16 +1,3 @@
1
- asset openmrs-esm-extensions.js 92 KiB [emitted] [minimized] (name: main) 1 related asset
2
- orphan modules 779 KiB [orphan] 666 modules
3
- runtime modules 670 bytes 3 modules
4
- built modules 234 KiB [built]
5
- modules by path external "@openmrs/ 252 bytes
6
- external "@openmrs/esm-config" 42 bytes [built] [code generated]
7
- external "@openmrs/esm-state" 42 bytes [built] [code generated]
8
- external "@openmrs/esm-api" 42 bytes [built] [code generated]
9
- external "@openmrs/esm-expression-evaluator" 42 bytes [built] [code generated]
10
- external "@openmrs/esm-feature-flags" 42 bytes [built] [code generated]
11
- external "@openmrs/esm-utils" 42 bytes [built] [code generated]
12
- cacheable modules 234 KiB
13
- ./src/index.ts + 115 modules 227 KiB [built] [code generated]
14
- ../esm-globals/dist/openmrs-esm-globals.js 6.29 KiB [built] [code generated]
15
- external "single-spa" 42 bytes [built] [code generated]
16
- webpack 5.88.0 compiled successfully in 10431 ms
1
+ [0] Successfully compiled: 8 files with swc (123.88ms)
2
+ [0] swc --strip-leading-paths src -d dist exited with code 0
3
+ [1] tsc --project tsconfig.build.json exited with code 0
@@ -0,0 +1,74 @@
1
+ /** @module @category Extension */
2
+ import { type AssignedExtension } from '.';
3
+ import { type ExtensionRegistration, type ExtensionInternalStore } from './store';
4
+ /**
5
+ * Given an extension ID, which is a string uniquely identifying
6
+ * an instance of an extension within an extension slot, this
7
+ * returns the extension name.
8
+ *
9
+ * @example
10
+ * ```js
11
+ * getExtensionNameFromId("foo#bar")
12
+ * --> "foo"
13
+ * getExtensionNameFromId("baz")
14
+ * --> "baz"
15
+ * ```
16
+ */
17
+ export declare function getExtensionNameFromId(extensionId: string): string;
18
+ export declare function getExtensionRegistrationFrom(state: ExtensionInternalStore, extensionId: string): ExtensionRegistration | undefined;
19
+ export declare function getExtensionRegistration(extensionId: string): ExtensionRegistration | undefined;
20
+ /**
21
+ * Extensions must be registered in order to be rendered.
22
+ * This is handled by the app shell, when extensions are provided
23
+ * via the `setupOpenMRS` return object.
24
+ * @internal
25
+ */
26
+ export declare const registerExtension: (extensionRegistration: ExtensionRegistration) => void;
27
+ /**
28
+ * Attach an extension to an extension slot.
29
+ *
30
+ * This will cause the extension to be rendered into the specified
31
+ * extension slot, unless it is removed by configuration. Using
32
+ * `attach` is an alternative to specifying the `slot` or `slots`
33
+ * in the extension declaration.
34
+ *
35
+ * It is particularly useful when creating a slot into which
36
+ * you want to render an existing extension. This enables you
37
+ * to do so without modifying the extension's declaration, which
38
+ * may be impractical or inappropriate, for example if you are
39
+ * writing a module for a specific implementation.
40
+ *
41
+ * @param slotName a name uniquely identifying the slot
42
+ * @param extensionId an extension name, with an optional #-suffix
43
+ * to distinguish it from other instances of the same extension
44
+ * attached to the same slot.
45
+ */
46
+ export declare function attach(slotName: string, extensionId: string): void;
47
+ /**
48
+ * @deprecated Avoid using this. Extension attachments should be considered declarative.
49
+ */
50
+ export declare function detach(extensionSlotName: string, extensionId: string): void;
51
+ /**
52
+ * @deprecated Avoid using this. Extension attachments should be considered declarative.
53
+ */
54
+ export declare function detachAll(extensionSlotName: string): void;
55
+ /**
56
+ * Gets the list of extensions assigned to a given slot
57
+ *
58
+ * @param slotName The slot to load the assigned extensions for
59
+ * @returns An array of extensions assigned to the named slot
60
+ */
61
+ export declare function getAssignedExtensions(slotName: string): Array<AssignedExtension>;
62
+ /**
63
+ * Used by by extension slots at mount time.
64
+ *
65
+ * @param moduleName The name of the module that contains the extension slot
66
+ * @param slotName The extension slot name that is actually used
67
+ * @internal
68
+ */
69
+ export declare const registerExtensionSlot: (moduleName: string, slotName: string) => void;
70
+ /**
71
+ * @internal
72
+ * Just for testing.
73
+ */
74
+ export declare const reset: () => void;
@@ -0,0 +1,351 @@
1
+ /** @module @category Extension */ /*
2
+ * We have the following extension modes:
3
+ *
4
+ * - attached (set via code in form of: attach, detach, ...)
5
+ * - configured (set via configuration in form of: added, removed, ...)
6
+ * - assigned (computed from attached and configured)
7
+ * - connected (computed from assigned using connectivity and online / offline)
8
+ */ import { sessionStore, userHasAccess } from "@openmrs/esm-api";
9
+ import { getExtensionConfigFromStore, getExtensionsConfigStore, getExtensionSlotConfig, getExtensionSlotConfigFromStore, getExtensionSlotsConfigStore } from "@openmrs/esm-config";
10
+ import { evaluateAsBoolean } from "@openmrs/esm-expression-evaluator";
11
+ import { featureFlagsStore } from "@openmrs/esm-feature-flags";
12
+ import { subscribeConnectivityChanged } from "@openmrs/esm-globals";
13
+ import { isOnline as isOnlineFn } from "@openmrs/esm-utils";
14
+ import { isEqual } from "lodash-es";
15
+ import { checkStatusFor, getExtensionInternalStore } from "./index.js";
16
+ import { getExtensionStore, updateInternalExtensionStore } from "./store.js";
17
+ const extensionInternalStore = getExtensionInternalStore();
18
+ const extensionStore = getExtensionStore();
19
+ const slotsConfigStore = getExtensionSlotsConfigStore();
20
+ const extensionsConfigStore = getExtensionsConfigStore();
21
+ // Keep the output store updated
22
+ function updateExtensionOutputStore(internalState, extensionSlotConfigs, extensionsConfigStore, featureFlagStore, sessionStore) {
23
+ const slots = {};
24
+ const isOnline = isOnlineFn();
25
+ const enabledFeatureFlags = Object.entries(featureFlagStore.flags).filter(([, { enabled }])=>enabled).map(([name])=>name);
26
+ for (let [slotName, slot] of Object.entries(internalState.slots)){
27
+ const { config } = getExtensionSlotConfigFromStore(extensionSlotConfigs, slot.name);
28
+ const assignedExtensions = getAssignedExtensionsFromSlotData(slotName, internalState, config, extensionsConfigStore, enabledFeatureFlags, isOnline, sessionStore.session);
29
+ slots[slotName] = {
30
+ moduleName: slot.moduleName,
31
+ assignedExtensions
32
+ };
33
+ }
34
+ if (!isEqual(extensionStore.getState().slots, slots)) {
35
+ extensionStore.setState({
36
+ slots
37
+ });
38
+ }
39
+ }
40
+ extensionInternalStore.subscribe((internalStore)=>{
41
+ updateExtensionOutputStore(internalStore, slotsConfigStore.getState(), extensionsConfigStore.getState(), featureFlagsStore.getState(), sessionStore.getState());
42
+ });
43
+ slotsConfigStore.subscribe((slotConfigs)=>{
44
+ updateExtensionOutputStore(extensionInternalStore.getState(), slotConfigs, extensionsConfigStore.getState(), featureFlagsStore.getState(), sessionStore.getState());
45
+ });
46
+ extensionsConfigStore.subscribe((extensionConfigs)=>{
47
+ updateExtensionOutputStore(extensionInternalStore.getState(), slotsConfigStore.getState(), extensionConfigs, featureFlagsStore.getState(), sessionStore.getState());
48
+ });
49
+ featureFlagsStore.subscribe((featureFlagStore)=>{
50
+ updateExtensionOutputStore(extensionInternalStore.getState(), slotsConfigStore.getState(), extensionsConfigStore.getState(), featureFlagStore, sessionStore.getState());
51
+ });
52
+ sessionStore.subscribe((session)=>{
53
+ updateExtensionOutputStore(extensionInternalStore.getState(), slotsConfigStore.getState(), extensionsConfigStore.getState(), featureFlagsStore.getState(), session);
54
+ });
55
+ function updateOutputStoreToCurrent() {
56
+ updateExtensionOutputStore(extensionInternalStore.getState(), slotsConfigStore.getState(), extensionsConfigStore.getState(), featureFlagsStore.getState(), sessionStore.getState());
57
+ }
58
+ updateOutputStoreToCurrent();
59
+ subscribeConnectivityChanged(updateOutputStoreToCurrent);
60
+ function createNewExtensionSlotInfo(slotName, moduleName) {
61
+ return {
62
+ moduleName,
63
+ name: slotName,
64
+ attachedIds: [],
65
+ config: null
66
+ };
67
+ }
68
+ /**
69
+ * Given an extension ID, which is a string uniquely identifying
70
+ * an instance of an extension within an extension slot, this
71
+ * returns the extension name.
72
+ *
73
+ * @example
74
+ * ```js
75
+ * getExtensionNameFromId("foo#bar")
76
+ * --> "foo"
77
+ * getExtensionNameFromId("baz")
78
+ * --> "baz"
79
+ * ```
80
+ */ export function getExtensionNameFromId(extensionId) {
81
+ const [extensionName] = extensionId.split('#');
82
+ return extensionName;
83
+ }
84
+ export function getExtensionRegistrationFrom(state, extensionId) {
85
+ const name = getExtensionNameFromId(extensionId);
86
+ return state.extensions[name];
87
+ }
88
+ export function getExtensionRegistration(extensionId) {
89
+ const state = extensionInternalStore.getState();
90
+ return getExtensionRegistrationFrom(state, extensionId);
91
+ }
92
+ /**
93
+ * Extensions must be registered in order to be rendered.
94
+ * This is handled by the app shell, when extensions are provided
95
+ * via the `setupOpenMRS` return object.
96
+ * @internal
97
+ */ export const registerExtension = (extensionRegistration)=>extensionInternalStore.setState((state)=>{
98
+ state.extensions[extensionRegistration.name] = {
99
+ ...extensionRegistration,
100
+ instances: []
101
+ };
102
+ return state;
103
+ });
104
+ /**
105
+ * Attach an extension to an extension slot.
106
+ *
107
+ * This will cause the extension to be rendered into the specified
108
+ * extension slot, unless it is removed by configuration. Using
109
+ * `attach` is an alternative to specifying the `slot` or `slots`
110
+ * in the extension declaration.
111
+ *
112
+ * It is particularly useful when creating a slot into which
113
+ * you want to render an existing extension. This enables you
114
+ * to do so without modifying the extension's declaration, which
115
+ * may be impractical or inappropriate, for example if you are
116
+ * writing a module for a specific implementation.
117
+ *
118
+ * @param slotName a name uniquely identifying the slot
119
+ * @param extensionId an extension name, with an optional #-suffix
120
+ * to distinguish it from other instances of the same extension
121
+ * attached to the same slot.
122
+ */ export function attach(slotName, extensionId) {
123
+ updateInternalExtensionStore((state)=>{
124
+ const existingSlot = state.slots[slotName];
125
+ if (!existingSlot) {
126
+ return {
127
+ ...state,
128
+ slots: {
129
+ ...state.slots,
130
+ [slotName]: {
131
+ ...createNewExtensionSlotInfo(slotName),
132
+ attachedIds: [
133
+ extensionId
134
+ ]
135
+ }
136
+ }
137
+ };
138
+ } else {
139
+ return {
140
+ ...state,
141
+ slots: {
142
+ ...state.slots,
143
+ [slotName]: {
144
+ ...existingSlot,
145
+ attachedIds: [
146
+ ...existingSlot.attachedIds,
147
+ extensionId
148
+ ]
149
+ }
150
+ }
151
+ };
152
+ }
153
+ });
154
+ }
155
+ /**
156
+ * @deprecated Avoid using this. Extension attachments should be considered declarative.
157
+ */ export function detach(extensionSlotName, extensionId) {
158
+ updateInternalExtensionStore((state)=>{
159
+ const existingSlot = state.slots[extensionSlotName];
160
+ if (existingSlot && existingSlot.attachedIds.includes(extensionId)) {
161
+ return {
162
+ ...state,
163
+ slots: {
164
+ ...state.slots,
165
+ [extensionSlotName]: {
166
+ ...existingSlot,
167
+ attachedIds: existingSlot.attachedIds.filter((id)=>id !== extensionId)
168
+ }
169
+ }
170
+ };
171
+ } else {
172
+ return state;
173
+ }
174
+ });
175
+ }
176
+ /**
177
+ * @deprecated Avoid using this. Extension attachments should be considered declarative.
178
+ */ export function detachAll(extensionSlotName) {
179
+ updateInternalExtensionStore((state)=>{
180
+ const existingSlot = state.slots[extensionSlotName];
181
+ if (existingSlot) {
182
+ return {
183
+ ...state,
184
+ slots: {
185
+ ...state.slots,
186
+ [extensionSlotName]: {
187
+ ...existingSlot,
188
+ attachedIds: []
189
+ }
190
+ }
191
+ };
192
+ } else {
193
+ return state;
194
+ }
195
+ });
196
+ }
197
+ /**
198
+ * Get an order index for the extension. This will
199
+ * come from either its configured order, its registered order
200
+ * parameter, or the order in which it happened to be attached.
201
+ */ function getOrder(extensionId, configuredOrder, registeredOrderIndex, attachedOrder) {
202
+ const configuredIndex = configuredOrder.indexOf(extensionId);
203
+ if (configuredIndex !== -1) {
204
+ return configuredIndex;
205
+ } else if (registeredOrderIndex !== undefined) {
206
+ // extensions that don't have a configured order should appear after those that do
207
+ return 1000 + registeredOrderIndex;
208
+ } else {
209
+ const assignedIndex = attachedOrder.indexOf(extensionId);
210
+ if (assignedIndex !== -1) {
211
+ // extensions that have neither a configured nor registered order should appear
212
+ // after all others
213
+ return 2000 + assignedIndex;
214
+ } else {
215
+ return -1;
216
+ }
217
+ }
218
+ }
219
+ function getAssignedExtensionsFromSlotData(slotName, internalState, config, extensionConfigStoreState, enabledFeatureFlags, isOnline, session) {
220
+ const attachedIds = internalState.slots[slotName].attachedIds;
221
+ const assignedIds = calculateAssignedIds(config, attachedIds);
222
+ const extensions = [];
223
+ for (let id of assignedIds){
224
+ const { config: extensionConfig } = getExtensionConfigFromStore(extensionConfigStoreState, slotName, id);
225
+ const name = getExtensionNameFromId(id);
226
+ const extension = internalState.extensions[name];
227
+ // if the extension has not been registered yet, do not include it
228
+ if (extension) {
229
+ const requiredPrivileges = extensionConfig?.['Display conditions']?.privileges ?? extension.privileges ?? [];
230
+ if (requiredPrivileges && (typeof requiredPrivileges === 'string' || Array.isArray(requiredPrivileges) && requiredPrivileges.length > 0)) {
231
+ if (!session?.user) {
232
+ continue;
233
+ }
234
+ if (!userHasAccess(requiredPrivileges, session.user)) {
235
+ continue;
236
+ }
237
+ }
238
+ const displayConditionExpression = extensionConfig?.['Display conditions']?.expression ?? null;
239
+ if (displayConditionExpression !== null) {
240
+ try {
241
+ if (!evaluateAsBoolean(displayConditionExpression, {
242
+ session
243
+ })) {
244
+ continue;
245
+ }
246
+ } catch (e) {
247
+ console.error(`Error while evaluating expression ${displayConditionExpression}`, e);
248
+ continue;
249
+ }
250
+ }
251
+ if (extension.featureFlag && !enabledFeatureFlags.includes(extension.featureFlag)) {
252
+ continue;
253
+ }
254
+ if (window.offlineEnabled && !checkStatusFor(isOnline, extension.online, extension.offline)) {
255
+ continue;
256
+ }
257
+ extensions.push({
258
+ id,
259
+ name,
260
+ moduleName: extension.moduleName,
261
+ config: extensionConfig,
262
+ featureFlag: extension.featureFlag,
263
+ meta: extension.meta,
264
+ online: extensionConfig?.['Display conditions']?.online ?? extension.online ?? true,
265
+ offline: extensionConfig?.['Display conditions']?.offline ?? extension.offline ?? false
266
+ });
267
+ }
268
+ }
269
+ return extensions;
270
+ }
271
+ /**
272
+ * Gets the list of extensions assigned to a given slot
273
+ *
274
+ * @param slotName The slot to load the assigned extensions for
275
+ * @returns An array of extensions assigned to the named slot
276
+ */ export function getAssignedExtensions(slotName) {
277
+ const internalState = extensionInternalStore.getState();
278
+ const { config: slotConfig } = getExtensionSlotConfig(slotName);
279
+ const extensionStoreState = extensionsConfigStore.getState();
280
+ const featureFlagState = featureFlagsStore.getState();
281
+ const sessionState = sessionStore.getState();
282
+ const isOnline = isOnlineFn();
283
+ const enabledFeatureFlags = Object.entries(featureFlagState.flags).filter(([, { enabled }])=>enabled).map(([name])=>name);
284
+ return getAssignedExtensionsFromSlotData(slotName, internalState, slotConfig, extensionStoreState, enabledFeatureFlags, isOnline, sessionState.session);
285
+ }
286
+ function calculateAssignedIds(config, attachedIds) {
287
+ const addedIds = config.add || [];
288
+ const removedIds = config.remove || [];
289
+ const idOrder = config.order || [];
290
+ const { extensions } = extensionInternalStore.getState();
291
+ return [
292
+ ...attachedIds,
293
+ ...addedIds
294
+ ].filter((id)=>!removedIds.includes(id)).sort((idA, idB)=>{
295
+ const ai = getOrder(idA, idOrder, extensions[getExtensionNameFromId(idA)]?.order, attachedIds);
296
+ const bi = getOrder(idB, idOrder, extensions[getExtensionNameFromId(idB)]?.order, attachedIds);
297
+ if (bi === -1) {
298
+ return -1;
299
+ } else if (ai === -1) {
300
+ return 1;
301
+ } else {
302
+ return ai - bi;
303
+ }
304
+ });
305
+ }
306
+ /**
307
+ * Used by by extension slots at mount time.
308
+ *
309
+ * @param moduleName The name of the module that contains the extension slot
310
+ * @param slotName The extension slot name that is actually used
311
+ * @internal
312
+ */ export const registerExtensionSlot = (moduleName, slotName)=>extensionInternalStore.setState((state)=>{
313
+ const existingModuleName = state.slots[slotName]?.moduleName;
314
+ if (existingModuleName && existingModuleName != moduleName) {
315
+ console.warn(`An extension slot with the name '${slotName}' already exists. Refusing to register the same slot name twice (in "registerExtensionSlot"). The existing one is from module ${existingModuleName}.`);
316
+ return state;
317
+ }
318
+ if (existingModuleName && existingModuleName == moduleName) {
319
+ // Re-rendering an existing slot
320
+ return state;
321
+ }
322
+ if (state.slots[slotName]) {
323
+ return {
324
+ ...state,
325
+ slots: {
326
+ ...state.slots,
327
+ [slotName]: {
328
+ ...state.slots[slotName],
329
+ moduleName
330
+ }
331
+ }
332
+ };
333
+ }
334
+ const slot = createNewExtensionSlotInfo(slotName, moduleName);
335
+ return {
336
+ ...state,
337
+ slots: {
338
+ ...state.slots,
339
+ [slotName]: slot
340
+ }
341
+ };
342
+ });
343
+ /**
344
+ * @internal
345
+ * Just for testing.
346
+ */ export const reset = ()=>extensionStore.setState(()=>{
347
+ return {
348
+ slots: {},
349
+ extensions: {}
350
+ };
351
+ });
@@ -0,0 +1,2 @@
1
+ export declare function checkStatus(online?: boolean | object, offline?: boolean | object): boolean;
2
+ export declare function checkStatusFor(status: boolean, online?: boolean | object, offline?: boolean | object): boolean;
@@ -0,0 +1,7 @@
1
+ import { isOnline } from "@openmrs/esm-utils";
2
+ export function checkStatus(online = true, offline = false) {
3
+ return checkStatusFor(isOnline(), online, offline);
4
+ }
5
+ export function checkStatusFor(status, online = true, offline = false) {
6
+ return Boolean(status ? online : offline);
7
+ }
@@ -0,0 +1,9 @@
1
+ export * from './store';
2
+ export * from './extensions';
3
+ export * from './modals';
4
+ export * from './workspaces';
5
+ export * from './helpers';
6
+ export * from './render';
7
+ /** @deprecated Use `getExtensionStore`. The structure of this store has also changed. */
8
+ declare const internalStore: import("zustand").StoreApi<import("./store").ExtensionInternalStore>;
9
+ export { internalStore as extensionStore };
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ export * from "./store.js";
2
+ export * from "./extensions.js";
3
+ export * from "./modals.js";
4
+ export * from "./workspaces.js";
5
+ export * from "./helpers.js";
6
+ export * from "./render.js";
7
+ // Temporary compatibility hack
8
+ // What is now `extensionInternalStore` used to be exposed
9
+ // and used as `extensionStore`.
10
+ import { getExtensionInternalStore } from "./store.js";
11
+ /** @deprecated Use `getExtensionStore`. The structure of this store has also changed. */ const internalStore = getExtensionInternalStore();
12
+ export { internalStore as extensionStore };
@@ -0,0 +1,13 @@
1
+ import type { LifeCycles } from 'single-spa';
2
+ /** @internal */
3
+ export interface ModalRegistration {
4
+ name: string;
5
+ load(): Promise<{
6
+ default?: LifeCycles;
7
+ } & LifeCycles>;
8
+ moduleName: string;
9
+ }
10
+ /** @internal */
11
+ export declare function registerModal(modalRegistration: ModalRegistration): void;
12
+ /** @internal */
13
+ export declare function getModalRegistration(modalName: string): ModalRegistration | undefined;
package/dist/modals.js ADDED
@@ -0,0 +1,28 @@
1
+ import { createGlobalStore } from "@openmrs/esm-state";
2
+ import { getExtensionRegistration } from "./index.js";
3
+ const modalRegistryStore = createGlobalStore('modalRegistry', {
4
+ modals: {}
5
+ });
6
+ /** @internal */ export function registerModal(modalRegistration) {
7
+ modalRegistryStore.setState((state)=>{
8
+ state.modals[modalRegistration.name] = modalRegistration;
9
+ return state;
10
+ });
11
+ }
12
+ /** @internal */ export function getModalRegistration(modalName) {
13
+ let modalRegistration = modalRegistryStore.getState().modals[modalName];
14
+ if (!modalRegistration) {
15
+ const extensionRegistration = getExtensionRegistration(modalName);
16
+ if (extensionRegistration) {
17
+ modalRegistration = {
18
+ name: modalName,
19
+ load: extensionRegistration.load,
20
+ moduleName: extensionRegistration.moduleName
21
+ };
22
+ console.warn(`Modal ${modalName} was registered as an extension. This is deprecated and will be removed in the future. Please register it in the "modals" section of routes.json instead of the "extensions" section.`);
23
+ // Register it so the warning only appears once
24
+ registerModal(modalRegistration);
25
+ }
26
+ }
27
+ return modalRegistration;
28
+ }
@@ -0,0 +1,4 @@
1
+ export { getExtensionNameFromId, registerExtension, attach, detach, detachAll, getAssignedExtensions, registerExtensionSlot, } from './extensions';
2
+ export { type CancelLoading, renderExtension } from './render';
3
+ export { type ExtensionMeta, type ExtensionRegistration, type ExtensionStore, type AssignedExtension, type ConnectedExtension, type ExtensionSlotState, getExtensionStore, } from './store';
4
+ export { type WorkspaceRegistration } from './workspaces';
package/dist/public.js ADDED
@@ -0,0 +1,3 @@
1
+ export { getExtensionNameFromId, registerExtension, attach, detach, detachAll, getAssignedExtensions, registerExtensionSlot } from "./extensions.js";
2
+ export { renderExtension } from "./render.js";
3
+ export { getExtensionStore } from "./store.js";
@@ -0,0 +1,11 @@
1
+ /** @module @category Extension */
2
+ import { type Parcel, type ParcelConfig } from 'single-spa';
3
+ export interface CancelLoading {
4
+ (): void;
5
+ }
6
+ /**
7
+ * Mounts into a DOM node (representing an extension slot)
8
+ * a lazy-loaded component from *any* frontend module
9
+ * that registered an extension component for this slot.
10
+ */
11
+ export declare function renderExtension(domElement: HTMLElement, extensionSlotName: string, extensionSlotModuleName: string, extensionId: string, renderFunction?: (application: ParcelConfig) => ParcelConfig, additionalProps?: Record<string, any>): Promise<Parcel | null>;
package/dist/render.js ADDED
@@ -0,0 +1,62 @@
1
+ /** @module @category Extension */ import { mountRootParcel } from "single-spa";
2
+ import { getExtensionNameFromId, getExtensionRegistration } from "./extensions.js";
3
+ import { checkStatus } from "./helpers.js";
4
+ import { updateInternalExtensionStore } from "./store.js";
5
+ let parcelCount = 0;
6
+ /**
7
+ * Mounts into a DOM node (representing an extension slot)
8
+ * a lazy-loaded component from *any* frontend module
9
+ * that registered an extension component for this slot.
10
+ */ export async function renderExtension(domElement, extensionSlotName, extensionSlotModuleName, extensionId, renderFunction = (x)=>x, additionalProps = {}) {
11
+ const extensionName = getExtensionNameFromId(extensionId);
12
+ const extensionRegistration = getExtensionRegistration(extensionId);
13
+ let parcel = null;
14
+ if (domElement) {
15
+ if (!extensionRegistration) {
16
+ throw Error(`Couldn't find extension '${extensionName}' to attach to '${extensionSlotName}'`);
17
+ }
18
+ const { load, meta, moduleName, online, offline } = extensionRegistration;
19
+ if (checkStatus(online, offline)) {
20
+ updateInternalExtensionStore((state)=>{
21
+ const instance = {
22
+ domElement,
23
+ id: extensionId,
24
+ slotName: extensionSlotName,
25
+ slotModuleName: extensionSlotModuleName
26
+ };
27
+ return {
28
+ ...state,
29
+ extensions: {
30
+ ...state.extensions,
31
+ [extensionName]: {
32
+ ...state.extensions[extensionName],
33
+ instances: [
34
+ ...state.extensions[extensionName].instances,
35
+ instance
36
+ ]
37
+ }
38
+ }
39
+ };
40
+ });
41
+ const { default: result, ...lifecycle } = await load();
42
+ const id = parcelCount++;
43
+ parcel = mountRootParcel(renderFunction({
44
+ ...result ?? lifecycle,
45
+ name: `${extensionSlotName}/${extensionName}-${id}`
46
+ }), {
47
+ ...additionalProps,
48
+ _meta: meta,
49
+ _extensionContext: {
50
+ extensionId,
51
+ extensionSlotName,
52
+ extensionSlotModuleName,
53
+ extensionModuleName: moduleName
54
+ },
55
+ domElement
56
+ });
57
+ }
58
+ } else {
59
+ console.warn(`Tried to render ${extensionId} into ${extensionSlotName} but no DOM element was available.`);
60
+ }
61
+ return parcel;
62
+ }