@equinor/fusion-framework-react-app 14.0.3 → 14.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/README.md +9 -2
  2. package/dist/esm/ag-grid/community.js +12 -0
  3. package/dist/esm/ag-grid/community.js.map +1 -0
  4. package/dist/esm/ag-grid/enterprise.js +12 -0
  5. package/dist/esm/ag-grid/enterprise.js.map +1 -0
  6. package/dist/esm/ag-grid/react.js +12 -0
  7. package/dist/esm/ag-grid/react.js.map +1 -0
  8. package/dist/esm/ag-grid/testing.js +19 -0
  9. package/dist/esm/ag-grid/testing.js.map +1 -0
  10. package/dist/esm/ag-grid/theme.js +13 -0
  11. package/dist/esm/ag-grid/theme.js.map +1 -0
  12. package/dist/esm/create-component.js +1 -1
  13. package/dist/esm/msal/useToken.js +12 -3
  14. package/dist/esm/msal/useToken.js.map +1 -1
  15. package/dist/esm/version.js +1 -1
  16. package/dist/tsconfig.tsbuildinfo +1 -1
  17. package/dist/types/ag-grid/community.d.ts +11 -0
  18. package/dist/types/ag-grid/enterprise.d.ts +11 -0
  19. package/dist/types/ag-grid/react.d.ts +11 -0
  20. package/dist/types/ag-grid/testing.d.ts +18 -0
  21. package/dist/types/ag-grid/theme.d.ts +13 -0
  22. package/dist/types/create-component.d.ts +1 -1
  23. package/dist/types/version.d.ts +1 -1
  24. package/package.json +80 -25
  25. package/CHANGELOG.md +0 -2730
  26. package/docs/ag-grid.md +0 -70
  27. package/docs/analytics.md +0 -122
  28. package/docs/app.md +0 -148
  29. package/docs/apploader.md +0 -110
  30. package/docs/bookmark.md +0 -101
  31. package/docs/context.md +0 -86
  32. package/docs/feature-flag.md +0 -119
  33. package/docs/framework.md +0 -93
  34. package/docs/help-center.md +0 -88
  35. package/docs/http.md +0 -118
  36. package/docs/msal.md +0 -291
  37. package/docs/navigation.md +0 -80
  38. package/docs/routing.md +0 -86
  39. package/docs/settings.md +0 -139
  40. package/src/__tests__/render-app.test.tsx +0 -113
  41. package/src/__tests__/useStateSyncEvents.test.ts +0 -57
  42. package/src/ag-grid/useTheme.ts +0 -21
  43. package/src/analytics/README.md +0 -148
  44. package/src/analytics/index.ts +0 -9
  45. package/src/analytics/useTrackFeature.ts +0 -73
  46. package/src/apploader/Apploader.tsx +0 -50
  47. package/src/apploader/README.md +0 -81
  48. package/src/apploader/index.ts +0 -10
  49. package/src/apploader/useApploader.ts +0 -107
  50. package/src/bookmark/index.ts +0 -20
  51. package/src/bookmark/useBookmark.ts +0 -25
  52. package/src/bookmark/useCurrentBookmark.ts +0 -54
  53. package/src/context/index.ts +0 -14
  54. package/src/context/useContextProvider.ts +0 -12
  55. package/src/context/useCurrentContext.ts +0 -20
  56. package/src/create-component.tsx +0 -120
  57. package/src/create-legacy-app.tsx +0 -51
  58. package/src/feature-flag/README.md +0 -20
  59. package/src/feature-flag/enable-feature-flag.ts +0 -79
  60. package/src/feature-flag/index.ts +0 -17
  61. package/src/feature-flag/useFeature.ts +0 -90
  62. package/src/framework/index.ts +0 -14
  63. package/src/framework/useFrameworkCurrentContext.ts +0 -15
  64. package/src/help-center/README.md +0 -21
  65. package/src/help-center/event-name.ts +0 -1
  66. package/src/help-center/index.ts +0 -11
  67. package/src/help-center/useHelpCenter.ts +0 -162
  68. package/src/http/index.ts +0 -10
  69. package/src/http/selectors.ts +0 -9
  70. package/src/index.ts +0 -46
  71. package/src/make-component.tsx +0 -102
  72. package/src/msal/index.ts +0 -16
  73. package/src/msal/useAccessToken.ts +0 -26
  74. package/src/msal/useCurrentAccount.ts +0 -21
  75. package/src/msal/useToken.ts +0 -52
  76. package/src/navigation/index.ts +0 -10
  77. package/src/navigation/useNavigationModule.ts +0 -10
  78. package/src/navigation/useRouter.ts +0 -35
  79. package/src/render-app.ts +0 -35
  80. package/src/render-component.tsx +0 -55
  81. package/src/routing/index.ts +0 -33
  82. package/src/settings/README.md +0 -123
  83. package/src/settings/index.ts +0 -12
  84. package/src/settings/useAppSetting.ts +0 -113
  85. package/src/settings/useAppSettings.ts +0 -103
  86. package/src/settings/useAppSettingsStatus.ts +0 -52
  87. package/src/state/index.ts +0 -12
  88. package/src/state/useAppState.ts +0 -299
  89. package/src/state/useStateSyncEvents.ts +0 -59
  90. package/src/useAppEnvironmentVariables.ts +0 -59
  91. package/src/useAppModule.ts +0 -45
  92. package/src/useAppModules.ts +0 -21
  93. package/src/version.ts +0 -2
  94. package/tsconfig.json +0 -48
  95. package/vitest.config.ts +0 -15
@@ -1,113 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import type { ComponentRenderArgs } from '../create-component';
3
- import type { Fusion } from '@equinor/fusion-framework-react';
4
- import type { AppEnv } from '@equinor/fusion-framework-app';
5
-
6
- // Mock dependencies before importing
7
- vi.mock('../create-component', () => ({
8
- createComponent: vi.fn(),
9
- }));
10
-
11
- vi.mock('../render-component', () => ({
12
- renderComponent: vi.fn(),
13
- }));
14
-
15
- import { renderApp } from '../render-app';
16
- import { createComponent } from '../create-component';
17
- import { renderComponent } from '../render-component';
18
-
19
- describe('renderApp', () => {
20
- let mockFusion: Fusion;
21
- let mockEnv: AppEnv;
22
- let mockElement: HTMLElement;
23
- let mockTeardown: () => void;
24
- let mockComponentRenderer: ReturnType<typeof createComponent>;
25
- let mockRenderFunction: ReturnType<typeof renderComponent>;
26
-
27
- beforeEach(() => {
28
- // Setup mock data
29
- mockFusion = {} as Fusion;
30
- mockEnv = {} as AppEnv;
31
- mockElement = document.createElement('div');
32
- mockTeardown = vi.fn();
33
-
34
- // Create a mock component renderer
35
- // createComponent returns a ComponentRenderer function: (fusion, env) => React.LazyExoticComponent
36
- mockComponentRenderer = vi.fn() as unknown as ReturnType<typeof createComponent>;
37
-
38
- // Create a mock render function
39
- mockRenderFunction = vi.fn(() => mockTeardown) as unknown as ReturnType<typeof renderComponent>;
40
-
41
- // Mock renderComponent to return our mock render function
42
- vi.mocked(renderComponent).mockReturnValue(mockRenderFunction);
43
-
44
- // Mock createComponent to return our mock component renderer
45
- vi.mocked(createComponent).mockReturnValue(mockComponentRenderer);
46
- });
47
-
48
- it('should create a render function that calls createComponent with provided arguments', () => {
49
- const TestComponent = () => <div>Test</div>;
50
- const mockConfigure = vi.fn();
51
-
52
- renderApp(TestComponent, mockConfigure);
53
-
54
- expect(createComponent).toHaveBeenCalledWith(TestComponent, mockConfigure);
55
- });
56
-
57
- it('should create a render function that uses renderComponent with the component renderer', () => {
58
- const TestComponent = () => <div>Test</div>;
59
- const appRender = renderApp(TestComponent);
60
-
61
- // Call the render function
62
- const teardown = appRender(mockElement, { fusion: mockFusion, env: mockEnv });
63
-
64
- expect(renderComponent).toHaveBeenCalledWith(mockComponentRenderer);
65
- expect(mockRenderFunction).toHaveBeenCalledWith(mockElement, {
66
- fusion: mockFusion,
67
- env: mockEnv,
68
- });
69
- expect(teardown).toBe(mockTeardown);
70
- });
71
-
72
- it('should return a teardown function that unmounts the component', () => {
73
- const TestComponent = () => <div>Test</div>;
74
- const appRender = renderApp(TestComponent);
75
-
76
- const teardown = appRender(mockElement, { fusion: mockFusion, env: mockEnv });
77
-
78
- expect(typeof teardown).toBe('function');
79
- teardown();
80
- expect(mockTeardown).toHaveBeenCalled();
81
- });
82
-
83
- it('should work without a configure callback', () => {
84
- vi.clearAllMocks();
85
- const TestComponent = () => <div>Test</div>;
86
- const appRender = renderApp(TestComponent);
87
-
88
- // When no configure callback is provided, it may be called with just the component
89
- // or with undefined as the second parameter
90
- expect(createComponent).toHaveBeenCalled();
91
- const calls = vi.mocked(createComponent).mock.calls;
92
- expect(calls[calls.length - 1][0]).toBe(TestComponent);
93
- expect(calls[calls.length - 1][1]).toBeUndefined();
94
-
95
- const teardown = appRender(mockElement, { fusion: mockFusion, env: mockEnv });
96
-
97
- expect(teardown).toBe(mockTeardown);
98
- });
99
-
100
- it('should pass component renderer args correctly to renderComponent', () => {
101
- const TestComponent = () => <div>Test</div>;
102
- const appRender = renderApp(TestComponent);
103
-
104
- const args: ComponentRenderArgs = {
105
- fusion: mockFusion,
106
- env: mockEnv,
107
- };
108
-
109
- appRender(mockElement, args);
110
-
111
- expect(mockRenderFunction).toHaveBeenCalledWith(mockElement, args);
112
- });
113
- });
@@ -1,57 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { Subject } from 'rxjs';
3
-
4
- import { act, renderHook } from '@testing-library/react';
5
-
6
- import { StateSyncEvent, type StateSyncEventType } from '@equinor/fusion-framework-module-state';
7
-
8
- const event$ = new Subject<StateSyncEventType>();
9
-
10
- vi.mock('../useAppModule', () => ({
11
- default: () => ({ event$ }),
12
- }));
13
-
14
- import { useStateSyncEvents } from '../state/useStateSyncEvents';
15
-
16
- describe('useStateSyncEvents', () => {
17
- it('collects dispatched onStateSync.* events, oldest first', () => {
18
- const { result } = renderHook(() => useStateSyncEvents(10));
19
-
20
- expect(result.current).toEqual([]);
21
-
22
- act(() => {
23
- event$.next(new StateSyncEvent.Status({ detail: { status: 'active' } }));
24
- event$.next(new StateSyncEvent.Status({ detail: { status: 'paused' } }));
25
- });
26
-
27
- expect(result.current).toHaveLength(2);
28
- expect(result.current[0].detail.status).toBe('active');
29
- expect(result.current[1].detail.status).toBe('paused');
30
- });
31
-
32
- it('ignores events unrelated to state sync and trims the log to the given limit', () => {
33
- const { result } = renderHook(() => useStateSyncEvents(1));
34
-
35
- act(() => {
36
- event$.next(new StateSyncEvent.Status({ detail: { status: 'active' } }));
37
- event$.next(
38
- new StateSyncEvent.Error({ detail: { error: new Error('boom'), type: 'error' } }),
39
- );
40
- });
41
-
42
- expect(result.current).toHaveLength(1);
43
- expect(result.current[0]).toBeInstanceOf(StateSyncEvent.Error);
44
- });
45
-
46
- it('unsubscribes from the event stream on unmount', () => {
47
- const { result, unmount } = renderHook(() => useStateSyncEvents(10));
48
-
49
- unmount();
50
-
51
- act(() => {
52
- event$.next(new StateSyncEvent.Status({ detail: { status: 'active' } }));
53
- });
54
-
55
- expect(result.current).toEqual([]);
56
- });
57
- });
@@ -1,21 +0,0 @@
1
- import { useAppModule } from '../useAppModule';
2
- import type { AgGridModule } from '@equinor/fusion-framework-module-ag-grid';
3
- import type { Theme } from '@equinor/fusion-framework-module-ag-grid/themes';
4
-
5
- /**
6
- * React hook that returns the current AG Grid theme from the application-scoped
7
- * AG Grid module.
8
- *
9
- * @returns The active {@link Theme} object.
10
- * @throws If the AG Grid module is not registered in the application.
11
- */
12
- export const useTheme = (): Theme => {
13
- const agGrid = useAppModule<AgGridModule>('agGrid');
14
-
15
- // Fail fast when the AG Grid module has not been registered on the app
16
- if (!agGrid) {
17
- throw new Error('agGrid module is not available');
18
- }
19
-
20
- return agGrid.theme as Theme;
21
- };
@@ -1,148 +0,0 @@
1
- ## Analytics React Hooks
2
-
3
- React hooks for tracking application feature usage through the Fusion analytics module.
4
-
5
- These hooks are available from `@equinor/fusion-framework-react-app/analytics`. The analytics module is enabled and configured by the hosting portal, so **apps running inside a Fusion portal can use these hooks immediately without any setup**.
6
-
7
- ### Why track feature usage?
8
-
9
- Understanding how users interact with your application helps you make informed decisions about what to build, improve, or remove. Without usage data, you are guessing which features matter.
10
-
11
- Common reasons to add feature tracking:
12
-
13
- - **Measure adoption** — find out whether a newly released feature is actually being used, and by how many users across different contexts.
14
- - **Identify unused features** — discover features that can be simplified or removed, reducing maintenance cost and complexity.
15
- - **Understand user workflows** — see which paths users take through your application, revealing unexpected patterns or friction points.
16
- - **Support data-driven prioritisation** — back up roadmap discussions with real usage numbers instead of assumptions.
17
- - **Debug user-reported issues** — correlate analytics events with error reports to understand what the user was doing when something went wrong.
18
-
19
- ### When to use `useTrackFeature`
20
-
21
- Use `useTrackFeature` when you want to record a discrete user action or application milestone from a React component. Typical use cases include:
22
-
23
- | Use case | What to track | Example event name |
24
- |----------|---------------|-------------------|
25
- | Button or action clicks | User triggers a specific workflow | `'export-clicked'`, `'filter-applied'` |
26
- | Page or component views | A section of the app is displayed | `'dashboard-loaded'`, `'settings-opened'` |
27
- | Feature gate checks | A feature behind a flag is accessed | `'beta-feature-used'` |
28
- | Form submissions | A user completes a form or wizard step | `'report-submitted'`, `'wizard-step-3'` |
29
- | Error recovery actions | A user retries or dismisses an error | `'retry-clicked'`, `'error-dismissed'` |
30
- | Search and filtering | A user interacts with data exploration tools | `'search-executed'`, `'date-range-changed'` |
31
-
32
- > [!TIP]
33
- > You do not need to track every click. Focus on actions that answer a question about user behavior or feature value.
34
-
35
- ### useTrackFeature
36
-
37
- Returns a stable callback for tracking feature usage events. Each event automatically includes the current app key and active context as attributes, so downstream dashboards can group events by application and context without extra work.
38
-
39
- **Signature:**
40
-
41
- ```typescript
42
- function useTrackFeature(): (name: string, data?: AnyValueMap) => void;
43
- ```
44
-
45
- | Parameter | Type | Description |
46
- |-----------|------|-------------|
47
- | `name` | `string` | Name of the feature being tracked |
48
- | `data` | `AnyValueMap` (optional) | Additional key-value pairs to include with the event |
49
-
50
- #### Track a button click
51
-
52
- ```tsx
53
- import { useCallback } from 'react';
54
- import { useTrackFeature } from '@equinor/fusion-framework-react-app/analytics';
55
-
56
- const MyButton = () => {
57
- const trackFeature = useTrackFeature();
58
-
59
- const handleClick = useCallback(() => {
60
- trackFeature('button-clicked');
61
- }, [trackFeature]);
62
-
63
- return <button onClick={handleClick}>Click me</button>;
64
- };
65
- ```
66
-
67
- #### Track with additional data
68
-
69
- Pass a second argument to include custom attributes with the event:
70
-
71
- ```tsx
72
- import { useCallback } from 'react';
73
- import { useTrackFeature } from '@equinor/fusion-framework-react-app/analytics';
74
-
75
- const SaveButton = ({ section }: { section: string }) => {
76
- const trackFeature = useTrackFeature();
77
-
78
- const handleSave = useCallback(() => {
79
- trackFeature('save-clicked', { section, timestamp: Date.now() });
80
- }, [trackFeature, section]);
81
-
82
- return <button onClick={handleSave}>Save</button>;
83
- };
84
- ```
85
-
86
- #### Track on component mount
87
-
88
- Use `useEffect` to track when a component or page loads:
89
-
90
- ```tsx
91
- import { useEffect } from 'react';
92
- import { useTrackFeature } from '@equinor/fusion-framework-react-app/analytics';
93
-
94
- const Dashboard = () => {
95
- const trackFeature = useTrackFeature();
96
-
97
- useEffect(() => {
98
- trackFeature('dashboard-loaded');
99
- }, [trackFeature]);
100
-
101
- return <div>Dashboard content</div>;
102
- };
103
- ```
104
-
105
- #### Combine click and mount tracking
106
-
107
- ```tsx
108
- import { useCallback, useEffect } from 'react';
109
- import { useTrackFeature } from '@equinor/fusion-framework-react-app/analytics';
110
-
111
- const SomeComponent = () => {
112
- const trackFeature = useTrackFeature();
113
-
114
- useEffect(() => {
115
- trackFeature('SomeComponent loaded');
116
- trackFeature('some feature happened', { extra: 'data', foo: 'bar' });
117
- }, [trackFeature]);
118
-
119
- const handleOnClick = useCallback(() => {
120
- trackFeature('button-clicked');
121
- }, [trackFeature]);
122
-
123
- return <button onClick={handleOnClick}>Click me</button>;
124
- };
125
- ```
126
-
127
- ### Prerequisites
128
-
129
- The analytics module must be enabled for these hooks to work. **In most cases, no app-level configuration is needed** — the hosting Fusion portal already enables and configures analytics with the appropriate adapters and collectors. Your app inherits this automatically.
130
-
131
- If the module is not available (e.g. in a standalone or custom portal setup), `useTrackFeature` logs an exception via the telemetry provider instead of throwing, so your app will not crash.
132
-
133
- #### Custom or standalone setups
134
-
135
- If you are building a custom portal or running outside the standard Fusion portal, you need to enable the analytics module yourself:
136
-
137
- ```typescript
138
- import { enableAnalytics } from '@equinor/fusion-framework-module-analytics';
139
- import { ConsoleAnalyticsAdapter } from '@equinor/fusion-framework-module-analytics/adapters';
140
-
141
- const configure = (configurator) => {
142
- enableAnalytics(configurator, (builder) => {
143
- builder.setAdapter('console', async () => new ConsoleAnalyticsAdapter());
144
- });
145
- };
146
- ```
147
-
148
- See the [analytics module documentation](https://equinor.github.io/fusion-framework/modules/analytics/) for adapter and collector setup.
@@ -1,9 +0,0 @@
1
- /**
2
- * Analytics sub-path entry-point.
3
- *
4
- * Provides the {@link useTrackFeature} hook for tracking application-level
5
- * analytics events through the Fusion analytics module.
6
- *
7
- * @packageDocumentation
8
- */
9
- export { useTrackFeature } from './useTrackFeature';
@@ -1,73 +0,0 @@
1
- import { useFrameworkModule } from '@equinor/fusion-framework-react';
2
- import type { AppModule } from '@equinor/fusion-framework-module-app';
3
- import type { AnalyticsModule, AnyValueMap } from '@equinor/fusion-framework-module-analytics';
4
- import { useCallback } from 'react';
5
-
6
- /**
7
- * React hook that returns a callback for tracking application feature usage
8
- * via the Fusion analytics module.
9
- *
10
- * The tracked event includes the current app key and context (if available)
11
- * as attributes, enabling downstream analytics dashboards to group events
12
- * by application and context.
13
- *
14
- * @returns A `trackFeature` callback: `(name: string, data?: AnyValueMap) => void`.
15
- *
16
- * @example
17
- * ```tsx
18
- * const trackFeature = useTrackFeature();
19
- *
20
- * const handleClick = useCallback(() => {
21
- * trackFeature('button-click', { section: 'header' });
22
- * }, [trackFeature]);
23
- * ```
24
- */
25
- export const useTrackFeature = () => {
26
- const analyticsProvider = useFrameworkModule<AnalyticsModule>('analytics');
27
- const appProvider = useFrameworkModule<AppModule>('app');
28
- const contextProvider = useFrameworkModule('context');
29
- const telemetryProvider = useFrameworkModule('telemetry');
30
-
31
- /**
32
- * Tracks a analytics event.
33
- *
34
- * Can be used both in useCallback or useEffects - see README.md for examples.
35
- *
36
- * @param name - The feature to track
37
- * @param data - Optional map of additional key-value pairs to include with the analytics event
38
- */
39
- const trackFeature = useCallback(
40
- (name: string, data?: AnyValueMap) => {
41
- // Report to telemetry when analytics tracking is not available so the gap is visible
42
- if (!analyticsProvider) {
43
- telemetryProvider?.trackException({
44
- name: 'AnalyticsProviderNotFound',
45
- exception: new Error(`Analytics provider not found`),
46
- });
47
- }
48
-
49
- analyticsProvider?.trackAnalytic({
50
- name: 'app-feature',
51
- value: {
52
- feature: name,
53
- data,
54
- },
55
- attributes: {
56
- appKey: appProvider?.current?.appKey,
57
- context: contextProvider?.currentContext
58
- ? {
59
- id: contextProvider.currentContext.id,
60
- externalId: contextProvider.currentContext.externalId,
61
- title: contextProvider.currentContext.title,
62
- type: contextProvider.currentContext.type.id,
63
- source: contextProvider.currentContext.source,
64
- }
65
- : undefined,
66
- },
67
- });
68
- },
69
- [analyticsProvider, appProvider, contextProvider, telemetryProvider],
70
- );
71
-
72
- return trackFeature;
73
- };
@@ -1,50 +0,0 @@
1
- import { useEffect, useRef, type ReactElement } from 'react';
2
- import { useApploader } from './useApploader';
3
-
4
- export type ApploaderProps = {
5
- appKey: string;
6
- };
7
-
8
- /**
9
- * Apploader component
10
- *
11
- * Embeds a Fusion child application inside a parent Fusion application.
12
- * Handles loading and error states, and mounts the child app's DOM element into a container div.
13
- *
14
- * @param { ApploaderProps } props - The props for the component.
15
- * @param { string } props.appKey - The key of the Fusion app to load and mount.
16
- * @returns { ReactElement } The rendered component, which displays loading, error, or the embedded app.
17
- *
18
- * @example
19
- * <Apploader appKey="my-app" />
20
- */
21
- export const Apploader = ({ appKey }: ApploaderProps): ReactElement => {
22
- const refWrapp = useRef<HTMLDivElement | null>(null);
23
- const { loading, error, appRef } = useApploader({ appKey });
24
-
25
- useEffect(() => {
26
- // Wait until both the wrapper element and the loaded app element are available
27
- if (!refWrapp.current || !appRef.current) {
28
- return;
29
- }
30
-
31
- refWrapp.current.appendChild(appRef.current);
32
- }, [appRef.current]);
33
-
34
- // Show a loading placeholder while the app is being fetched and initialized
35
- if (loading) {
36
- return <div>Loading {appKey}</div>;
37
- }
38
- // Surface the load error instead of rendering the app
39
- if (error) {
40
- return (
41
- <div>
42
- Error loading {appKey}. Error: {error.message}
43
- </div>
44
- );
45
- }
46
-
47
- return <div ref={refWrapp} />;
48
- };
49
-
50
- export default Apploader;
@@ -1,81 +0,0 @@
1
- # `@equinor/fusion-framework-react-app/apploader`
2
-
3
- > [!NOTE]
4
- > This file is kept for legacy tooling. The canonical documentation is in [`packages/react/app/docs/apploader.md`](../../docs/apploader.md) and published via VuePress.
5
-
6
- [Apploader](#apploader) component and [useApploader](#useapploader) is intended to be used to embed Fusion applications inside other Fusion application.
7
-
8
- > [!WARNING]
9
- > `Apploader` is an experimental poc.
10
- >
11
- > The embedded application will likely have issues with routing, context and other framework functionality, so use with care.
12
- >
13
- > Should only be used to embed 'simple' applications like **PowerBI** and **PowerApps**.
14
-
15
- ## Apploader
16
-
17
- React component for embeding a Fusion child application inside a parent Fusion application.
18
-
19
- Handles loading and error states, and mounts the child app's DOM element into a container div.
20
-
21
- If you need to customise the error and loading messages, then use the hook ``useApploader`` and create your own component.
22
-
23
- ### Example usage
24
-
25
- ```typescript
26
- <Apploader appKey="my-app" />
27
- ```
28
-
29
- ## useApploader
30
-
31
- A React hook for dynamically loading and mounting a Fusion child app inside a parent Fusion app. Handles loading state, error reporting, and provides a reference to the mounted app’s DOM element.
32
-
33
- ### Signature
34
-
35
- ```typescript
36
- useApploader({ appKey }: { appKey: string }): {
37
- loading: boolean;
38
- error: Error | undefined;
39
- appRef: React.RefObject<HTMLDivElement | null>;
40
- }
41
- ```
42
-
43
- ### Parameters
44
-
45
- `appKey (string)`: The key of the Fusion app to load and mount.
46
-
47
- ### Returns
48
-
49
- - **loading** ``(boolean)``: true while the app is loading.
50
- - **error** ``(Error | undefined)``: Error object if loading fails, otherwise undefined.
51
- - **appRef** ``(React.RefObject<HTMLDivElement | null>)``: Ref to the DOM element where the child app is mounted.
52
-
53
- ### Usage Example
54
-
55
- ```typescript
56
- import React, { useEffect, useRef } from 'react';
57
- import { useApploader } from './useAppLoader';
58
-
59
- const MyAppLoader = ({ appKey }: { appKey: string }) => {
60
- const wrapperRef = useRef<HTMLDivElement | null>(null);
61
- const { loading, error, appRef } = useApploader({ appKey });
62
-
63
- useEffect(() => {
64
- if (wrapperRef.current && appRef.current) {
65
- wrapperRef.current.appendChild(appRef.current);
66
- }
67
- }, [appRef.current]);
68
-
69
- if (loading) return <div>Loading {appKey}...</div>;
70
- if (error) return <div>Error loading {appKey}: {error.message}</div>;
71
-
72
- return <div ref={wrapperRef} />;
73
- };
74
- ```
75
-
76
- ### Notes
77
-
78
- - The hook is designed to be used in a parent Fusion app context.
79
- - The returned appRef should be appended to a container element in your component.
80
- - Handles subscription and cleanup automatically.
81
- - Useful for micro-frontend scenarios where apps are loaded dynamically.
@@ -1,10 +0,0 @@
1
- /**
2
- * Apploader sub-path entry-point.
3
- *
4
- * Provides the {@link Apploader} component and {@link useApploader} hook
5
- * for embedding Fusion child applications inside a parent Fusion application.
6
- *
7
- * @packageDocumentation
8
- */
9
- export { Apploader, type ApploaderProps } from './Apploader';
10
- export { useApploader } from './useApploader';
@@ -1,107 +0,0 @@
1
- import { useEffect, useMemo, useRef, useState } from 'react';
2
-
3
- import { useFramework } from '@equinor/fusion-framework-react';
4
- import type { AppModule } from '@equinor/fusion-framework-module-app';
5
-
6
- import type { ApploaderProps } from './Apploader';
7
- import type { AppInitializeResult } from '@equinor/fusion-framework-module-app/app';
8
-
9
- /**
10
- * React hook for dynamically loading and mounting a Fusion child app inside a parent Fusion app.
11
- * Handles loading state, error reporting, and provides a reference to the mounted app’s DOM element.
12
- *
13
- * @param { Object } params
14
- * @param { string } params.appKey - The key of the Fusion app to load and mount.
15
- * @returns {{
16
- * loading: boolean,
17
- * error: Error | undefined,
18
- * appRef: React.RefObject<HTMLDivElement | null>
19
- * }} An object containing loading state, error, and a ref to the mounted app element.
20
- *
21
- * @example
22
- * ```typescript
23
- * const { loading, error, appRef } = useApploader({ appKey: 'my-app' });
24
- *
25
- * useEffect(() => {
26
- * if (containerRef.current && appRef.current) {
27
- * containerRef.current.appendChild(appRef.current);
28
- * }
29
- * }, [appRef.current]);
30
- *
31
- * if (loading) return <div>Loading...</div>;
32
- * if (error) return <div>Error: {error.message}</div>;
33
- * return <div ref={containerRef} />;
34
- * ```
35
- */
36
- export const useApploader = ({
37
- appKey,
38
- }: ApploaderProps): {
39
- loading: boolean;
40
- error: Error | undefined;
41
- appRef: React.RefObject<HTMLDivElement | null>;
42
- } => {
43
- const [loading, setLoading] = useState(true);
44
- const [error, setError] = useState<Error | undefined>();
45
- const appRef = useRef<HTMLDivElement | null>(null);
46
- const fusion = useFramework<[AppModule]>();
47
-
48
- /**
49
- * The current fusion app.
50
- * aka the parent app that is loading the child app.
51
- */
52
- const fusionApp = useMemo(() => fusion.modules.app.current, [fusion]);
53
-
54
- /**
55
- * The app to be mounted
56
- * aka the child app that is being loaded.
57
- */
58
- const loadedApp = useMemo(() => fusion.modules.app.createApp({ appKey }), [fusion, appKey]);
59
-
60
- useEffect(() => {
61
- setLoading(true);
62
- setError(undefined);
63
- const subscription$ = loadedApp?.initialize().subscribe({
64
- next: (instance: AppInitializeResult) => {
65
- const { manifest, script, config } = instance;
66
- /* Application Element for mounting */
67
- appRef.current = document.createElement('div');
68
- appRef.current.id = manifest.appKey;
69
- appRef.current.style.display = 'contents';
70
-
71
- /* Use basename of current fusionApp */
72
- const basename = `/apps/${fusionApp?.appKey}`;
73
-
74
- try {
75
- const render = script.renderApp ?? script.default;
76
- // Prefer the modern renderApp export, falling back to the default export
77
- if (render) {
78
- return render(appRef.current, {
79
- fusion,
80
- env: { basename, config, manifest, props: {} },
81
- });
82
- }
83
- throw Error('Application is not supported, no render function provided');
84
- } catch (error) {
85
- console.error('App loading Error: ', error);
86
- setError(error as Error);
87
- }
88
- },
89
- complete: () => {
90
- setLoading(false);
91
- },
92
- error: (error: Error) => {
93
- setError(error);
94
- setLoading(false);
95
- },
96
- });
97
- return () => {
98
- subscription$.unsubscribe();
99
- };
100
- }, [fusionApp, loadedApp, fusion]);
101
-
102
- return {
103
- loading,
104
- error,
105
- appRef,
106
- };
107
- };