@equinor/fusion-framework-react-app 12.0.2 → 12.0.4

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 (66) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/esm/ag-grid/useTheme.js +1 -0
  3. package/dist/esm/ag-grid/useTheme.js.map +1 -1
  4. package/dist/esm/analytics/useTrackFeature.js +1 -0
  5. package/dist/esm/analytics/useTrackFeature.js.map +1 -1
  6. package/dist/esm/apploader/Apploader.js +4 -1
  7. package/dist/esm/apploader/Apploader.js.map +1 -1
  8. package/dist/esm/apploader/useApploader.js +1 -0
  9. package/dist/esm/apploader/useApploader.js.map +1 -1
  10. package/dist/esm/bookmark/useBookmark.js +0 -1
  11. package/dist/esm/bookmark/useBookmark.js.map +1 -1
  12. package/dist/esm/bookmark/useCurrentBookmark.js +1 -0
  13. package/dist/esm/bookmark/useCurrentBookmark.js.map +1 -1
  14. package/dist/esm/create-component.js +2 -0
  15. package/dist/esm/create-component.js.map +1 -1
  16. package/dist/esm/create-legacy-app.js +4 -2
  17. package/dist/esm/create-legacy-app.js.map +1 -1
  18. package/dist/esm/feature-flag/enable-feature-flag.js +4 -1
  19. package/dist/esm/feature-flag/enable-feature-flag.js.map +1 -1
  20. package/dist/esm/feature-flag/useFeature.js +6 -2
  21. package/dist/esm/feature-flag/useFeature.js.map +1 -1
  22. package/dist/esm/help-center/event-name.js +2 -0
  23. package/dist/esm/help-center/event-name.js.map +1 -0
  24. package/dist/esm/help-center/useHelpCenter.js +1 -1
  25. package/dist/esm/help-center/useHelpCenter.js.map +1 -1
  26. package/dist/esm/msal/useToken.js +1 -0
  27. package/dist/esm/msal/useToken.js.map +1 -1
  28. package/dist/esm/settings/useAppSetting.js +2 -0
  29. package/dist/esm/settings/useAppSetting.js.map +1 -1
  30. package/dist/esm/settings/useAppSettings.js +7 -2
  31. package/dist/esm/settings/useAppSettings.js.map +1 -1
  32. package/dist/esm/settings/useAppSettingsStatus.js +4 -0
  33. package/dist/esm/settings/useAppSettingsStatus.js.map +1 -1
  34. package/dist/esm/useAppEnvironmentVariables.js +1 -0
  35. package/dist/esm/useAppEnvironmentVariables.js.map +1 -1
  36. package/dist/esm/useAppModule.js +1 -0
  37. package/dist/esm/useAppModule.js.map +1 -1
  38. package/dist/esm/version.js +1 -1
  39. package/dist/tsconfig.tsbuildinfo +1 -1
  40. package/dist/types/bookmark/useBookmark.d.ts +1 -1
  41. package/dist/types/help-center/event-name.d.ts +1 -0
  42. package/dist/types/help-center/index.d.ts +1 -1
  43. package/dist/types/help-center/useHelpCenter.d.ts +0 -1
  44. package/dist/types/navigation/useRouter.d.ts +1 -1
  45. package/dist/types/version.d.ts +1 -1
  46. package/package.json +21 -21
  47. package/src/ag-grid/useTheme.ts +1 -0
  48. package/src/analytics/useTrackFeature.ts +1 -0
  49. package/src/apploader/Apploader.tsx +3 -0
  50. package/src/apploader/useApploader.ts +1 -0
  51. package/src/bookmark/useBookmark.ts +1 -2
  52. package/src/bookmark/useCurrentBookmark.ts +1 -0
  53. package/src/create-component.tsx +3 -1
  54. package/src/create-legacy-app.tsx +4 -2
  55. package/src/feature-flag/enable-feature-flag.ts +4 -1
  56. package/src/feature-flag/useFeature.ts +10 -2
  57. package/src/help-center/event-name.ts +1 -0
  58. package/src/help-center/index.ts +1 -1
  59. package/src/help-center/useHelpCenter.ts +2 -2
  60. package/src/msal/useToken.ts +1 -0
  61. package/src/settings/useAppSetting.ts +2 -0
  62. package/src/settings/useAppSettings.ts +7 -2
  63. package/src/settings/useAppSettingsStatus.ts +4 -0
  64. package/src/useAppEnvironmentVariables.ts +1 -0
  65. package/src/useAppModule.ts +1 -0
  66. package/src/version.ts +1 -1
@@ -16,5 +16,5 @@ import { type useBookmarkResult } from '@equinor/fusion-framework-react-module-b
16
16
  * @template TData - Current applications bookmark type
17
17
  * @return {*} {Bookmarks<TData>} the full api fro handling bookmarks
18
18
  */
19
- export declare const useBookmark: <TData>() => useBookmarkResult;
19
+ export declare const useBookmark: <_TData>() => useBookmarkResult;
20
20
  export default useBookmark;
@@ -0,0 +1 @@
1
+ export declare const EVENT_NAME = "@Portal::FusionHelp::open";
@@ -1,7 +1,7 @@
1
1
  import type { FrameworkEvent, FrameworkEventInit } from '@equinor/fusion-framework-module-event';
2
2
  import type { HelpCenterOpenEventDetail } from './useHelpCenter';
3
3
  export { useHelpCenter } from './useHelpCenter';
4
- export type { HelpCenterOpenEventDetail };
4
+ export type { HelpCenterOpenEventDetail } from './useHelpCenter';
5
5
  declare module '@equinor/fusion-framework-module-event' {
6
6
  interface FrameworkEventMap {
7
7
  '@Portal::FusionHelp::open': FrameworkEvent<FrameworkEventInit<HelpCenterOpenEventDetail>>;
@@ -11,7 +11,6 @@ type OpenSearchDetail = HelpCenterEventDetailBase<'search', {
11
11
  }>;
12
12
  type OpenGovernanceDetail = HelpCenterEventDetailBase<'governance'>;
13
13
  export type HelpCenterOpenEventDetail = OpenHelpDetail | OpenArticleDetail | OpenFaqsDetail | OpenSearchDetail | OpenGovernanceDetail;
14
- export declare const EVENT_NAME = "@Portal::FusionHelp::open";
15
14
  /**
16
15
  * Interface for requesting help features.
17
16
  */
@@ -24,4 +24,4 @@ import type { INavigationProvider } from '@equinor/fusion-framework-module-navig
24
24
  * };
25
25
  * ```
26
26
  */
27
- export declare const useRouter: (routes: Parameters<INavigationProvider["createRouter"]>[0]) => ReturnType<INavigationProvider["createRouter"]>;
27
+ export declare const useRouter: (routes: Parameters<INavigationProvider['createRouter']>[0]) => ReturnType<INavigationProvider['createRouter']>;
@@ -1 +1 @@
1
- export declare const version = "12.0.2";
1
+ export declare const version = "12.0.4";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@equinor/fusion-framework-react-app",
3
- "version": "12.0.2",
3
+ "version": "12.0.4",
4
4
  "description": "",
5
5
  "main": "./dist/esm/index.js",
6
6
  "types": "./dist/types/index.d.ts",
@@ -124,14 +124,14 @@
124
124
  "directory": "packages/react"
125
125
  },
126
126
  "dependencies": {
127
- "@equinor/fusion-framework-app": "11.0.10",
128
- "@equinor/fusion-framework-module": "6.1.0",
129
- "@equinor/fusion-framework-module-app": "8.0.2",
130
- "@equinor/fusion-framework-module-navigation": "7.0.4",
131
- "@equinor/fusion-framework-module-http": "8.0.3",
132
- "@equinor/fusion-framework-react-module": "4.0.1",
133
- "@equinor/fusion-framework-react-module-http": "11.0.0",
134
- "@equinor/fusion-framework-react": "8.0.0"
127
+ "@equinor/fusion-framework-module": "6.1.1",
128
+ "@equinor/fusion-framework-module-http": "8.0.4",
129
+ "@equinor/fusion-framework-module-navigation": "7.0.6",
130
+ "@equinor/fusion-framework-react": "8.0.1",
131
+ "@equinor/fusion-framework-app": "11.0.11",
132
+ "@equinor/fusion-framework-react-module": "4.0.2",
133
+ "@equinor/fusion-framework-module-app": "8.0.3",
134
+ "@equinor/fusion-framework-react-module-http": "11.0.1"
135
135
  },
136
136
  "devDependencies": {
137
137
  "@types/react": "^19.2.7",
@@ -140,25 +140,25 @@
140
140
  "react": "^19.2.1",
141
141
  "react-dom": "^19.2.1",
142
142
  "rxjs": "^7.8.1",
143
- "typescript": "^6.0.3",
143
+ "typescript": "^7.0.2",
144
144
  "vitest": "^4.1.0",
145
- "@equinor/fusion-framework-module-ag-grid": "37.0.0",
146
- "@equinor/fusion-framework-module-analytics": "3.0.1",
147
- "@equinor/fusion-framework-module-event": "6.0.0",
148
- "@equinor/fusion-framework-module-feature-flag": "2.0.1",
149
- "@equinor/fusion-framework-module-msal": "10.0.1",
150
- "@equinor/fusion-framework-react-module-bookmark": "6.0.0",
151
- "@equinor/fusion-framework-react-module-context": "7.0.1",
152
- "@equinor/fusion-framework-react-router": "2.2.0",
153
- "@equinor/fusion-observable": "9.1.0"
145
+ "@equinor/fusion-framework-module-ag-grid": "37.0.1",
146
+ "@equinor/fusion-framework-module-analytics": "3.0.3",
147
+ "@equinor/fusion-framework-module-feature-flag": "2.0.2",
148
+ "@equinor/fusion-framework-module-msal": "10.0.2",
149
+ "@equinor/fusion-framework-react-module-bookmark": "6.0.1",
150
+ "@equinor/fusion-framework-module-event": "6.0.1",
151
+ "@equinor/fusion-framework-react-module-context": "7.0.2",
152
+ "@equinor/fusion-framework-react-router": "2.4.0",
153
+ "@equinor/fusion-observable": "9.1.1"
154
154
  },
155
155
  "peerDependencies": {
156
156
  "@types/react": "^18.0.0 || ^19.0.0",
157
157
  "react": "^18.0.0 || ^19.0.0",
158
158
  "react-dom": "^18.0.0 || ^19.0.0",
159
159
  "rxjs": "^7.0.0",
160
- "@equinor/fusion-framework-react-router": "^2.2.0",
161
- "@equinor/fusion-framework-module-msal": "^10.0.1"
160
+ "@equinor/fusion-framework-module-msal": "^10.0.2",
161
+ "@equinor/fusion-framework-react-router": "^2.4.0"
162
162
  },
163
163
  "peerDependenciesMeta": {
164
164
  "@equinor/fusion-framework-react-module-ag-grid": {
@@ -12,6 +12,7 @@ import type { Theme } from '@equinor/fusion-framework-module-ag-grid/themes';
12
12
  export const useTheme = (): Theme => {
13
13
  const agGrid = useAppModule<AgGridModule>('agGrid');
14
14
 
15
+ // Fail fast when the AG Grid module has not been registered on the app
15
16
  if (!agGrid) {
16
17
  throw new Error('agGrid module is not available');
17
18
  }
@@ -38,6 +38,7 @@ export const useTrackFeature = () => {
38
38
  */
39
39
  const trackFeature = useCallback(
40
40
  (name: string, data?: AnyValueMap) => {
41
+ // Report to telemetry when analytics tracking is not available so the gap is visible
41
42
  if (!analyticsProvider) {
42
43
  telemetryProvider?.trackException({
43
44
  name: 'AnalyticsProviderNotFound',
@@ -23,6 +23,7 @@ export const Apploader = ({ appKey }: ApploaderProps): ReactElement => {
23
23
  const { loading, error, appRef } = useApploader({ appKey });
24
24
 
25
25
  useEffect(() => {
26
+ // Wait until both the wrapper element and the loaded app element are available
26
27
  if (!refWrapp.current || !appRef.current) {
27
28
  return;
28
29
  }
@@ -30,9 +31,11 @@ export const Apploader = ({ appKey }: ApploaderProps): ReactElement => {
30
31
  refWrapp.current.appendChild(appRef.current);
31
32
  }, [appRef.current]);
32
33
 
34
+ // Show a loading placeholder while the app is being fetched and initialized
33
35
  if (loading) {
34
36
  return <div>Loading {appKey}</div>;
35
37
  }
38
+ // Surface the load error instead of rendering the app
36
39
  if (error) {
37
40
  return (
38
41
  <div>
@@ -73,6 +73,7 @@ export const useApploader = ({
73
73
 
74
74
  try {
75
75
  const render = script.renderApp ?? script.default;
76
+ // Prefer the modern renderApp export, falling back to the default export
76
77
  if (render) {
77
78
  return render(appRef.current, {
78
79
  fusion,
@@ -20,7 +20,6 @@ import {
20
20
  * @template TData - Current applications bookmark type
21
21
  * @return {*} {Bookmarks<TData>} the full api fro handling bookmarks
22
22
  */
23
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
24
- export const useBookmark = <TData>(): useBookmarkResult => _useBookmark();
23
+ export const useBookmark = <_TData>(): useBookmarkResult => _useBookmark();
25
24
 
26
25
  export default useBookmark;
@@ -29,6 +29,7 @@ export const useCurrentBookmark = <TData extends BookmarkData>(
29
29
  const { currentApp } = useCurrentApp();
30
30
  const appBookmarkProvider = useAppModules<[BookmarkModule]>().bookmark;
31
31
  const frameworkBookmarkProvider = useFrameworkModule<BookmarkModule>('bookmark');
32
+ // Warn about the deprecated fallback to the framework-scoped bookmark provider
32
33
  if (!appBookmarkProvider) {
33
34
  console.warn(
34
35
  '@deprecation',
@@ -81,6 +81,8 @@ export const createComponent =
81
81
  (fusion, env) =>
82
82
  lazy(async () => {
83
83
  const init = configureModules<TModules, TRef, TEnv>(configure);
84
+ // configureModules returns a generic modules instance; narrow it to the
85
+ // app-specific shape expected by consumers of this component.
84
86
  const modules = (await init({
85
87
  fusion,
86
88
  env,
@@ -94,7 +96,7 @@ export const createComponent =
94
96
  default: () => (
95
97
  <FrameworkProvider value={fusion}>
96
98
  <AppModuleProvider value={modules}>
97
- {/* TODO */}
99
+ {/* TODO(#5083): tighten generics so Component no longer needs @ts-ignore */}
98
100
  {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment*/}
99
101
  {/* @ts-ignore */}
100
102
  <Component />
@@ -27,16 +27,18 @@ export const createLegacyApp = <TModules extends Array<AnyModule>>(
27
27
  ) => {
28
28
  const LegacyComponent = () => {
29
29
  const fusion = useFramework<[AppModule]>();
30
- // biome-ignore lint/correctness/useExhaustiveDependencies: this will soon be removed
31
30
  const RenderComponent = useMemo(() => {
32
31
  const creator = createComponent(Component, configure);
33
32
  // @eikeland
34
- // TODO - recheck when legacy fusion-cli is updated!
33
+ // TODO(#5084): recheck when legacy fusion-cli is updated!
35
34
  const appProvider = fusion.modules.app;
35
+ // Use the real app config/manifest once the provider has resolved them
36
36
  if (appProvider?.current) {
37
37
  const { config, manifest } = appProvider.current;
38
+ // Legacy app provider config/manifest predate the strict AppEnv shape; cast until legacy fusion-cli is updated.
38
39
  return creator(fusion, { config, manifest } as unknown as AppEnv);
39
40
  }
41
+ // No app provider available yet; pass an empty object cast to AppEnv as a placeholder.
40
42
  return creator(fusion, {} as unknown as AppEnv);
41
43
  }, [fusion]);
42
44
  return (
@@ -47,6 +47,7 @@ export function enableFeatureFlag(
47
47
  | Array<IFeatureFlag<unknown> & { allowUrl?: boolean | undefined }>
48
48
  | FeatureFlagBuilderCallback,
49
49
  ): void {
50
+ // Dispatch based on whether the caller passed an array of flags or a builder callback
50
51
  switch (typeof flags_cb) {
51
52
  case 'function': {
52
53
  enableFeatureFlagging(configurator, flags_cb);
@@ -54,15 +55,17 @@ export function enableFeatureFlag(
54
55
  }
55
56
  case 'object': {
56
57
  const urlFlags: IFeatureFlag[] = [];
58
+ // Split out flags marked allowUrl so they can be registered with the URL plugin
57
59
  const localFlags = (flags_cb ?? []).map((flag) => {
58
60
  const { allowUrl, ...localFlag } = flag;
61
+ // Track allowUrl flags separately; they still get registered locally too
59
62
  if (allowUrl) {
60
63
  urlFlags.push(flag);
61
64
  }
62
65
  return localFlag;
63
66
  });
64
67
  enableFeatureFlagging(configurator, async (builder) => {
65
- // TODO: should unnamed application be allowed to use feature flags?
68
+ // TODO(#5086): should unnamed application be allowed to use feature flags?
66
69
  builder.addPlugin(
67
70
  createLocalStoragePlugin(localFlags, {
68
71
  name: (configurator as AppConfigurator).env?.manifest.appKey ?? undefined,
@@ -56,13 +56,21 @@ export const useFeature = <T = unknown>(
56
56
  app: appProvider.features$,
57
57
  }).pipe(
58
58
  map(({ framework, app }) => {
59
- return { ...framework, ...app };
59
+ // Application feature flags override framework defaults with the same key.
60
+ const mergedFeatureFlags = { ...framework, ...app };
61
+ return mergedFeatureFlags;
60
62
  }),
61
63
  );
62
64
  }, [appProvider, frameworkProvider]);
63
65
 
64
66
  /** find feature flag by the provided key */
65
- const feature$ = useMemo(() => features$.pipe(findFeature<T>(key)), [features$, key]);
67
+ const feature$ = useMemo(
68
+ () =>
69
+ features$
70
+ // Narrow the merged stream down to the requested feature key.
71
+ .pipe(findFeature<T>(key)),
72
+ [features$, key],
73
+ );
66
74
 
67
75
  const { value: feature, error } = useObservableState(feature$ ?? EMPTY);
68
76
 
@@ -0,0 +1 @@
1
+ export const EVENT_NAME = '@Portal::FusionHelp::open';
@@ -2,7 +2,7 @@ import type { FrameworkEvent, FrameworkEventInit } from '@equinor/fusion-framewo
2
2
  import type { HelpCenterOpenEventDetail } from './useHelpCenter';
3
3
 
4
4
  export { useHelpCenter } from './useHelpCenter';
5
- export type { HelpCenterOpenEventDetail };
5
+ export type { HelpCenterOpenEventDetail } from './useHelpCenter';
6
6
 
7
7
  declare module '@equinor/fusion-framework-module-event' {
8
8
  interface FrameworkEventMap {
@@ -1,6 +1,8 @@
1
1
  import { useCallback } from 'react';
2
2
  import useAppModule from '../useAppModule';
3
3
 
4
+ import { EVENT_NAME } from './event-name.js';
5
+
4
6
  type HelpCenterEventDetailBase<
5
7
  T extends string,
6
8
  V extends Record<string, unknown> = Record<string, unknown>,
@@ -35,8 +37,6 @@ export type HelpCenterOpenEventDetail =
35
37
  | OpenSearchDetail
36
38
  | OpenGovernanceDetail;
37
39
 
38
- export const EVENT_NAME = '@Portal::FusionHelp::open';
39
-
40
40
  /**
41
41
  * Interface for requesting help features.
42
42
  */
@@ -38,6 +38,7 @@ export const useToken = (req: {
38
38
  msalProvider
39
39
  .acquireToken({ request: req })
40
40
  .then((result) => {
41
+ // Only update state when a token was actually acquired
41
42
  if (result) {
42
43
  setToken(result);
43
44
  }
@@ -69,6 +69,7 @@ export const useAppSetting = <
69
69
  }, [currentApp]);
70
70
 
71
71
  useLayoutEffect(() => {
72
+ // Narrow the settings stream down to just this specific setting's value
72
73
  const sub = currentApp?.settings$
73
74
  .pipe(map((settings) => (settings as TSettings)[prop]))
74
75
  .subscribe(subject);
@@ -81,6 +82,7 @@ export const useAppSetting = <
81
82
  // update function
82
83
  const setSetting = useCallback(
83
84
  (update: TSettings[TProp] | UpdateSettingFunction<TSettings[TProp]>) => {
85
+ // Cannot persist a setting update without a current app to write it to
84
86
  if (!currentApp) {
85
87
  return onError?.(new Error('App is not available'));
86
88
  }
@@ -63,8 +63,12 @@ export const useAppSettings = <TSettings extends Record<string, unknown> = AppSe
63
63
 
64
64
  // connect the subject to the current app settings stream
65
65
  useLayoutEffect(() => {
66
- const sub = (currentApp?.settings$ as Observable<TSettings>).subscribe(subject);
67
- return () => sub?.unsubscribe();
66
+ // Nothing to subscribe to until an app is resolved
67
+ if (!currentApp) {
68
+ return;
69
+ }
70
+ const sub = (currentApp.settings$ as Observable<TSettings>).subscribe(subject);
71
+ return () => sub.unsubscribe();
68
72
  }, [currentApp, subject]);
69
73
 
70
74
  // subscribe to the subject to get the latest settings
@@ -72,6 +76,7 @@ export const useAppSettings = <TSettings extends Record<string, unknown> = AppSe
72
76
 
73
77
  const setSettings = useCallback(
74
78
  (update: TSettings | UpdateSettingsFunction<TSettings>) => {
79
+ // Cannot persist settings updates without a current app to write them to
75
80
  if (!currentApp) {
76
81
  return onError?.(new Error('App is not available'));
77
82
  }
@@ -29,7 +29,9 @@ export const useAppSettingsStatus = (app: IApp | null, hooks?: AppSettingsStatus
29
29
  const { onLoading, onUpdating } = hooks ?? {};
30
30
 
31
31
  useLayoutEffect(() => {
32
+ // Only subscribe once both an app and an onLoading callback are available
32
33
  if (app && onLoading) {
34
+ // Derive a boolean loading flag from whether 'fetch_settings' is an active status
33
35
  const subscription = app.status$
34
36
  .pipe(map((status) => status.has('fetch_settings')))
35
37
  .subscribe(onLoading);
@@ -38,7 +40,9 @@ export const useAppSettingsStatus = (app: IApp | null, hooks?: AppSettingsStatus
38
40
  }, [app, onLoading]);
39
41
 
40
42
  useLayoutEffect(() => {
43
+ // Only subscribe once both an app and an onUpdating callback are available
41
44
  if (app && onUpdating) {
45
+ // Derive a boolean updating flag from whether 'update_settings' is an active status
42
46
  const subscription = app.status$
43
47
  .pipe(map((status) => status.has('update_settings')))
44
48
  .subscribe(onUpdating);
@@ -44,6 +44,7 @@ export const useAppEnvironmentVariables = <
44
44
  }
45
45
 
46
46
  const env$ = useMemo(() => {
47
+ // Extract just the environment slice of the app's config for consumers
47
48
  return app.getConfig().pipe(
48
49
  map((config) => {
49
50
  return config.environment as TEnvironmentVariables;
@@ -33,6 +33,7 @@ export function useAppModule<
33
33
  ? ModuleType<TType>
34
34
  : AppModulesInstance[Extract<keyof AppModulesInstance, TKey>] {
35
35
  const appModule = useAppModules()[module as keyof AppModulesInstance];
36
+ // Fail fast when the requested module was not registered on the app scope
36
37
  if (!appModule) {
37
38
  throw Error(`the requested module [${module}] is not included in the app scope`);
38
39
  }
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- export const version = '12.0.2';
2
+ export const version = '12.0.4';