@equinor/fusion-framework-react-app 14.1.0 → 14.1.2

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 (93) hide show
  1. package/dist/esm/version.js +1 -1
  2. package/dist/tsconfig.tsbuildinfo +1 -1
  3. package/dist/types/version.d.ts +1 -1
  4. package/package.json +33 -30
  5. package/CHANGELOG.md +0 -2785
  6. package/docs/ag-grid.md +0 -70
  7. package/docs/analytics.md +0 -122
  8. package/docs/app.md +0 -148
  9. package/docs/apploader.md +0 -110
  10. package/docs/bookmark.md +0 -108
  11. package/docs/context.md +0 -88
  12. package/docs/feature-flag.md +0 -119
  13. package/docs/framework.md +0 -93
  14. package/docs/help-center.md +0 -88
  15. package/docs/http.md +0 -118
  16. package/docs/msal.md +0 -291
  17. package/docs/navigation.md +0 -80
  18. package/docs/routing.md +0 -86
  19. package/docs/settings.md +0 -139
  20. package/src/__tests__/Apploader.test.tsx +0 -51
  21. package/src/__tests__/fixtures/apploader-child-script.ts +0 -9
  22. package/src/__tests__/render-app.test.tsx +0 -113
  23. package/src/__tests__/testApp.test.tsx +0 -76
  24. package/src/__tests__/useAccessToken.test.tsx +0 -51
  25. package/src/__tests__/useAppSetting.test.tsx +0 -133
  26. package/src/__tests__/useAppSettings.test.tsx +0 -147
  27. package/src/__tests__/useCurrentAccount.test.tsx +0 -32
  28. package/src/__tests__/useCurrentBookmark.test.tsx +0 -108
  29. package/src/__tests__/useCurrentContext.test.tsx +0 -72
  30. package/src/__tests__/useFeature.test.tsx +0 -104
  31. package/src/__tests__/useHelpCenter.test.tsx +0 -64
  32. package/src/__tests__/useStateSyncEvents.test.ts +0 -58
  33. package/src/__tests__/useToken.test.tsx +0 -71
  34. package/src/__tests__/useTrackFeature.test.tsx +0 -83
  35. package/src/ag-grid/community.ts +0 -11
  36. package/src/ag-grid/enterprise.ts +0 -11
  37. package/src/ag-grid/react.ts +0 -11
  38. package/src/ag-grid/testing.ts +0 -19
  39. package/src/ag-grid/theme.ts +0 -17
  40. package/src/ag-grid/useTheme.ts +0 -21
  41. package/src/analytics/README.md +0 -148
  42. package/src/analytics/index.ts +0 -9
  43. package/src/analytics/useTrackFeature.ts +0 -73
  44. package/src/apploader/Apploader.tsx +0 -50
  45. package/src/apploader/README.md +0 -81
  46. package/src/apploader/index.ts +0 -10
  47. package/src/apploader/useApploader.ts +0 -107
  48. package/src/bookmark/index.ts +0 -20
  49. package/src/bookmark/useBookmark.ts +0 -25
  50. package/src/bookmark/useCurrentBookmark.ts +0 -54
  51. package/src/context/index.ts +0 -14
  52. package/src/context/useContextProvider.ts +0 -12
  53. package/src/context/useCurrentContext.ts +0 -20
  54. package/src/create-component.tsx +0 -120
  55. package/src/create-legacy-app.tsx +0 -51
  56. package/src/feature-flag/README.md +0 -20
  57. package/src/feature-flag/enable-feature-flag.ts +0 -79
  58. package/src/feature-flag/index.ts +0 -17
  59. package/src/feature-flag/useFeature.ts +0 -90
  60. package/src/framework/index.ts +0 -14
  61. package/src/framework/useFrameworkCurrentContext.ts +0 -15
  62. package/src/help-center/README.md +0 -21
  63. package/src/help-center/event-name.ts +0 -1
  64. package/src/help-center/index.ts +0 -11
  65. package/src/help-center/useHelpCenter.ts +0 -162
  66. package/src/http/index.ts +0 -10
  67. package/src/http/selectors.ts +0 -9
  68. package/src/index.ts +0 -46
  69. package/src/make-component.tsx +0 -102
  70. package/src/msal/index.ts +0 -16
  71. package/src/msal/useAccessToken.ts +0 -26
  72. package/src/msal/useCurrentAccount.ts +0 -21
  73. package/src/msal/useToken.ts +0 -63
  74. package/src/navigation/index.ts +0 -10
  75. package/src/navigation/useNavigationModule.ts +0 -10
  76. package/src/navigation/useRouter.ts +0 -35
  77. package/src/render-app.ts +0 -35
  78. package/src/render-component.tsx +0 -55
  79. package/src/routing/index.ts +0 -33
  80. package/src/settings/README.md +0 -123
  81. package/src/settings/index.ts +0 -12
  82. package/src/settings/useAppSetting.ts +0 -113
  83. package/src/settings/useAppSettings.ts +0 -103
  84. package/src/settings/useAppSettingsStatus.ts +0 -52
  85. package/src/state/index.ts +0 -12
  86. package/src/state/useAppState.ts +0 -299
  87. package/src/state/useStateSyncEvents.ts +0 -59
  88. package/src/useAppEnvironmentVariables.ts +0 -59
  89. package/src/useAppModule.ts +0 -45
  90. package/src/useAppModules.ts +0 -21
  91. package/src/version.ts +0 -2
  92. package/tsconfig.json +0 -54
  93. package/vitest.config.ts +0 -9
@@ -1,103 +0,0 @@
1
- import { useCallback, useLayoutEffect, useMemo } from 'react';
2
- import { BehaviorSubject, type Observable } from 'rxjs';
3
-
4
- import type { AppSettings } from '@equinor/fusion-framework-module-app';
5
-
6
- import { useCurrentApp } from '@equinor/fusion-framework-react/app';
7
- import { useObservableState } from '@equinor/fusion-observable/react';
8
-
9
- import { useAppSettingsStatus, type AppSettingsStatusHooks } from './useAppSettingsStatus';
10
-
11
- type UpdateSettingsFunction<T, O = T> = (currentSettings: T | undefined) => O;
12
-
13
- /**
14
- * Custom hook to manage application settings.
15
- *
16
- * @template TSettings - The type of the settings object, extending Record<string, any>. Defaults to AppSettings.
17
- *
18
- * @param {TSettings} [defaultValue] - The default value for the settings.
19
- * @param hooks - Optional hooks to handle the status changes and errors.
20
- *
21
- * @remarks
22
- * `defaultValue` will only be used on the first render.
23
- * `hooks`must be memoized to avoid unnecessary re-renders.
24
- *
25
- * @returns {Array} An array containing:
26
- * - `settings`: The current settings object.
27
- * - `setSettings`: A function to update the settings.
28
- *
29
- * @example
30
- * const [settings, setSettings] = useAppSettings();
31
- *
32
- * @example
33
- * const [settings, setSettings] = useAppSettings({ theme: 'dark' });
34
- *
35
- * @example
36
- * const [isLoading, setIsLoading] = useState(false);
37
- * const [isUpdating, setIsUpdating] = useState(false);
38
- * const [error, setError] = useState<Error | null>(null);
39
- *
40
- * const onUpdated = useCallback(() => console.log('Settings updated'), []);
41
- *
42
- * const [settings, setSettings] = useAppSettings({ theme: 'dark' }, {
43
- * onLoading: setIsLoading,
44
- * onUpdating: setIsUpdating,
45
- * onError: setError,
46
- * onUpdated,
47
- * });
48
- */
49
- export const useAppSettings = <TSettings extends Record<string, unknown> = AppSettings>(
50
- defaultValue?: TSettings,
51
- hooks?: AppSettingsStatusHooks & {
52
- onError?: (error: Error | null) => void;
53
- onUpdated?: () => void;
54
- },
55
- ): [TSettings, (settings: TSettings | UpdateSettingsFunction<TSettings>) => void] => {
56
- const { onError, onUpdated, onLoading, onUpdating } = hooks ?? {};
57
- const { currentApp = null } = useCurrentApp();
58
-
59
- // biome-ignore lint/correctness/useExhaustiveDependencies: create new subject when app changes
60
- const subject = useMemo(() => {
61
- return new BehaviorSubject<TSettings>(defaultValue ?? ({} as TSettings));
62
- }, [currentApp]);
63
-
64
- // connect the subject to the current app settings stream
65
- useLayoutEffect(() => {
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();
72
- }, [currentApp, subject]);
73
-
74
- // subscribe to the subject to get the latest settings
75
- const { value: settings } = useObservableState(subject, { initial: defaultValue });
76
-
77
- const setSettings = useCallback(
78
- (update: TSettings | UpdateSettingsFunction<TSettings>) => {
79
- // Cannot persist settings updates without a current app to write them to
80
- if (!currentApp) {
81
- return onError?.(new Error('App is not available'));
82
- }
83
-
84
- // resolve settings with the provided value or function
85
- const settings = typeof update === 'function' ? update(subject.value) : update;
86
-
87
- currentApp.updateSettings(settings).subscribe({
88
- next: () => {
89
- onUpdated?.();
90
- onError?.(null);
91
- },
92
- error: onError,
93
- });
94
- },
95
- [currentApp, subject, onError, onUpdated],
96
- );
97
-
98
- useAppSettingsStatus(currentApp, { onLoading, onUpdating });
99
-
100
- return [settings, setSettings];
101
- };
102
-
103
- export default useAppSettings;
@@ -1,52 +0,0 @@
1
- import { useLayoutEffect } from 'react';
2
- import { map } from 'rxjs';
3
-
4
- import type { IApp } from '@equinor/fusion-framework-module-app';
5
-
6
- export type AppSettingsStatusHooks = {
7
- onLoading?: (isLoading: boolean) => void;
8
- onUpdating?: (isUpdating: boolean) => void;
9
- };
10
-
11
- /**
12
- * Custom hook to handle app settings status updates.
13
- *
14
- * @param {IApp | null} app - The app instance to monitor settings status.
15
- * @param {AppSettingsStatusHooks} [hooks] - Optional hooks to handle loading and updating status.
16
- * @param {function} [hooks.onLoading] - Callback function to handle loading status.
17
- * @param {function} [hooks.onUpdating] - Callback function to handle updating status.
18
- *
19
- * @returns {void}
20
- *
21
- * @example
22
- * const hooks = useMemo(() => ({
23
- * onLoading: (isLoading) => console.log('Loading:', isLoading),
24
- * onUpdating: (isUpdating) => console.log('Updating:', isUpdating),
25
- * }, []);
26
- * useAppSettingsStatus(app, hooks);
27
- */
28
- export const useAppSettingsStatus = (app: IApp | null, hooks?: AppSettingsStatusHooks) => {
29
- const { onLoading, onUpdating } = hooks ?? {};
30
-
31
- useLayoutEffect(() => {
32
- // Only subscribe once both an app and an onLoading callback are available
33
- if (app && onLoading) {
34
- // Derive a boolean loading flag from whether 'fetch_settings' is an active status
35
- const subscription = app.status$
36
- .pipe(map((status) => status.has('fetch_settings')))
37
- .subscribe(onLoading);
38
- return () => subscription.unsubscribe();
39
- }
40
- }, [app, onLoading]);
41
-
42
- useLayoutEffect(() => {
43
- // Only subscribe once both an app and an onUpdating callback are available
44
- if (app && onUpdating) {
45
- // Derive a boolean updating flag from whether 'update_settings' is an active status
46
- const subscription = app.status$
47
- .pipe(map((status) => status.has('update_settings')))
48
- .subscribe(onUpdating);
49
- return () => subscription.unsubscribe();
50
- }
51
- }, [app, onUpdating]);
52
- };
@@ -1,12 +0,0 @@
1
- export {
2
- AllowedValue,
3
- IStateProvider,
4
- StateItem,
5
- StateSyncEvent,
6
- type StateSyncEventType,
7
- } from '@equinor/fusion-framework-module-state';
8
-
9
- export { enableState as enableAppState } from '@equinor/fusion-framework-app/enable-state';
10
-
11
- export { useAppState } from './useAppState';
12
- export { useStateSyncEvents } from './useStateSyncEvents';
@@ -1,299 +0,0 @@
1
- import {
2
- useCallback,
3
- useLayoutEffect,
4
- useRef,
5
- useState,
6
- useSyncExternalStore,
7
- type SetStateAction,
8
- } from 'react';
9
-
10
- import { BehaviorSubject, from } from 'rxjs';
11
- import { map, skip } from 'rxjs/operators';
12
-
13
- import type { AllowedValue, StateModule } from '@equinor/fusion-framework-module-state';
14
-
15
- import useAppModule from '../useAppModule';
16
-
17
- /**
18
- * Configuration options for the `useAppState` hook.
19
- */
20
- interface UseAppStateOptions<T extends AllowedValue> {
21
- /**
22
- * Default value to return when the state item doesn't exist or is undefined.
23
- * This value will be returned immediately on first render if no stored value exists.
24
- */
25
- defaultValue?: T;
26
- }
27
-
28
- /**
29
- * A React hook for managing persistent application state through the Fusion Framework.
30
- *
31
- * This hook provides a simple way to store and retrieve values that persist across
32
- * app sessions and are shared between different parts of your application. It works
33
- * similarly to `useState` but with automatic persistence and cross-component synchronization.
34
- *
35
- * **Key Features:**
36
- * - Automatic persistence across app sessions
37
- * - Real-time synchronization between components
38
- * - Optimistic updates for responsive UX
39
- * - Deep equality checking to prevent unnecessary re-renders
40
- * - TypeScript support with type safety
41
- * - Default value support
42
- *
43
- * **Important Notes:**
44
- * - Setting a value to `undefined` will completely remove it from storage
45
- * - Values must be serializable (JSON-compatible)
46
- * - Use unique keys to avoid conflicts between different state items
47
- * - **The key parameter must remain constant across re-renders** - changing the key after the hook
48
- * is initialized will be ignored and a warning will be logged in development mode
49
- *
50
- * @template T The type of value to store. Must be serializable (string, number, boolean, object, array, etc.)
51
- * @param key Unique identifier for this state item. Use descriptive names like 'user.preferences' or 'dashboard.filters'. **Must remain constant across re-renders.**
52
- * @param options Configuration options including default value
53
- * @returns A tuple containing [currentValue, setValue] similar to useState
54
- *
55
- * @example
56
- * **Basic Usage:**
57
- * ```tsx
58
- * function UserProfile() {
59
- * // Simple string state with default value
60
- * const [userName, setUserName] = useAppState('user.name', {
61
- * defaultValue: 'Anonymous'
62
- * });
63
- *
64
- * return (
65
- * <input
66
- * value={userName || ''}
67
- * onChange={(e) => setUserName(e.target.value)}
68
- * />
69
- * );
70
- * }
71
- * ```
72
- *
73
- * @example
74
- * **Object State:**
75
- * ```tsx
76
- * interface UserSettings {
77
- * theme: 'light' | 'dark';
78
- * language: string;
79
- * notifications: boolean;
80
- * }
81
- *
82
- * function SettingsPanel() {
83
- * const [settings, setSettings] = useAppState<UserSettings>('user.settings', {
84
- * defaultValue: { theme: 'light', language: 'en', notifications: true }
85
- * });
86
- *
87
- * const toggleTheme = () => {
88
- * setSettings(prev => ({
89
- * ...prev!,
90
- * theme: prev!.theme === 'light' ? 'dark' : 'light'
91
- * }));
92
- * };
93
- *
94
- * return <button onClick={toggleTheme}>Theme: {settings?.theme}</button>;
95
- * }
96
- * ```
97
- *
98
- * @example
99
- * **Array State:**
100
- * ```tsx
101
- * function TaskList() {
102
- * const [tasks, setTasks] = useAppState<string[]>('tasks', { defaultValue: [] });
103
- *
104
- * const addTask = (text: string) => {
105
- * setTasks(prev => [...(prev || []), text]);
106
- * };
107
- *
108
- * const removeTask = (index: number) => {
109
- * setTasks(prev => prev?.filter((_, i) => i !== index));
110
- * };
111
- *
112
- * return (
113
- * <ul>
114
- * {tasks?.map((task, index) => (
115
- * <li key={index} onClick={() => removeTask(index)}>
116
- * {task}
117
- * </li>
118
- * ))}
119
- * </ul>
120
- * );
121
- * }
122
- * ```
123
- *
124
- * @example
125
- * **Clearing State:**
126
- * ```tsx
127
- * function DataManager() {
128
- * const [data, setData] = useAppState<unknown[]>('cache.data');
129
- *
130
- * const clearCache = () => {
131
- * // Setting to undefined removes the item from storage completely
132
- * setData(undefined);
133
- * };
134
- *
135
- * return <button onClick={clearCache}>Clear Cache</button>;
136
- * }
137
- * ```
138
- *
139
- * @example
140
- * **Cross-Component Synchronization:**
141
- * ```tsx
142
- * // Component A
143
- * function ComponentA() {
144
- * const [counter, setCounter] = useAppState('shared.counter', { defaultValue: 0 });
145
- * return <button onClick={() => setCounter(c => (c || 0) + 1)}>Count: {counter}</button>;
146
- * }
147
- *
148
- * // Component B (automatically stays in sync)
149
- * function ComponentB() {
150
- * const [counter] = useAppState('shared.counter', { defaultValue: 0 });
151
- * return <div>Current count: {counter}</div>;
152
- * }
153
- * ```
154
- *
155
- * @example
156
- * **Key Stability - DO and DON'T:**
157
- * ```tsx
158
- * function MyComponent({ userId }: { userId: string }) {
159
- * // ❌ DON'T: Key changes with prop, will cause warnings and use initial key
160
- * const [userPrefs] = useAppState(`user.${userId}.preferences`);
161
- *
162
- * // ✅ DO: Use a constant key
163
- * const [globalSettings] = useAppState('app.global.settings');
164
- *
165
- * return <div>...</div>;
166
- * }
167
- * ```
168
- *
169
- * @since 6.3.0
170
- */
171
- export const useAppState = <T extends AllowedValue = AllowedValue>(
172
- key: string,
173
- options?: UseAppStateOptions<T>,
174
- ): [T | undefined, (action: SetStateAction<T | undefined>) => void] => {
175
- // Restrict development-only key validation to avoid production overhead.
176
- if (process.env.NODE_ENV === 'development') {
177
- // Warn early when callers provide a key that cannot identify persisted state.
178
- if (!key || typeof key !== 'string') {
179
- console.warn('useAppState: key must be a non-empty string');
180
- }
181
- }
182
-
183
- // Capture the initial key value and ensure it never changes
184
- const initialKey = useRef(key);
185
-
186
- // Warn about key changes only in development because the hook intentionally keeps its initial key.
187
- if (process.env.NODE_ENV === 'development') {
188
- // Surface an unstable key while preserving the original storage identity.
189
- if (initialKey.current !== key) {
190
- console.warn(
191
- `useAppState: key changed from "${initialKey.current}" to "${key}". The key should remain constant across re-renders. Using initial key: "${initialKey.current}"`,
192
- );
193
- }
194
- }
195
-
196
- // Access the state module from the Fusion Framework's dependency injection system
197
- const stateProvider = useAppModule<StateModule>('state');
198
-
199
- // BehaviorSubject bridges async state provider with React's sync rendering.
200
- // Provides immediate access via value$.value and replay semantics for new subscribers.
201
- // Initialize with defaultValue for consistent SSR/client hydration.
202
- const [value$] = useState(() => {
203
- return new BehaviorSubject<T | undefined>(options?.defaultValue);
204
- });
205
-
206
- // useLayoutEffect runs synchronously after DOM mutations but before paint,
207
- // preventing visual inconsistencies during hydration and ensuring state sync before updates.
208
- useLayoutEffect(() => {
209
- const subscription = from(
210
- stateProvider.observeItem<T>(initialKey.current, { initialValue: value$.value }),
211
- ).subscribe({
212
- next: (item) => {
213
- // Convert state provider's null to undefined for React conventions
214
- value$.next(item === null ? undefined : item.value);
215
- },
216
- error: (err) => {
217
- // Log errors for debugging but don't crash the component
218
- console.error(`State observation error for key "${initialKey.current}":`, err);
219
- },
220
- complete: () => {
221
- // Complete the local stream when the source completes
222
- value$.complete();
223
- },
224
- });
225
-
226
- // Critical: Always cleanup subscriptions to prevent memory leaks
227
- return () => {
228
- subscription.unsubscribe();
229
- };
230
- }, [stateProvider, value$]);
231
-
232
- // Helper function to get the current value, falling back to the default if necessary
233
- const getValue = useCallback(
234
- (rawValue: T | undefined) => (rawValue === undefined ? options?.defaultValue : rawValue),
235
- [options?.defaultValue],
236
- );
237
-
238
- // useSyncExternalStore integrates with React 18's concurrent features,
239
- // ensuring consistent state during concurrent rendering and preventing tearing.
240
- const value = useSyncExternalStore(
241
- (callback) => {
242
- const subscription = value$
243
- // Transform state emissions into the snapshot updates expected by React.
244
- .pipe(
245
- // skip the initial value, since we don't want to emit anything before the app state provider has initialized
246
- skip(1),
247
- // Apply default value logic consistently with snapshot function
248
- map((value) => (value === undefined ? options?.defaultValue : value)),
249
- )
250
- .subscribe(callback);
251
-
252
- return () => {
253
- subscription.unsubscribe();
254
- };
255
- },
256
-
257
- // Snapshot function: returns current value synchronously for React rendering
258
- () => getValue(value$.value),
259
-
260
- // Server snapshot: ensures consistent hydration between server and client
261
- () => options?.defaultValue,
262
- );
263
-
264
- // Implements optimistic updates: update local state immediately, then persist.
265
- // If persistence fails, the state provider will emit the old value, reverting the update.
266
- const setValue = useCallback(
267
- (action: SetStateAction<T | undefined>) => {
268
- // Handle both direct values and updater functions (like React's useState).
269
- // Apply the same defaulting as the snapshot so updaters never see a bare `undefined`
270
- // when a `defaultValue` was provided.
271
- const value = typeof action === 'function' ? action(getValue(value$.value)) : action;
272
-
273
- // Remove undefined values from storage while updating subscribers immediately.
274
- if (value === undefined) {
275
- // undefined means "delete from storage" - update local state first for immediate UI feedback
276
- value$.next(undefined);
277
- stateProvider.removeItem(initialKey.current).catch((error) => {
278
- console.error(`Failed to remove item "${initialKey.current}":`, error);
279
- });
280
- } else {
281
- // Optimistic update: local state first, then persist to storage
282
- value$.next(value);
283
- stateProvider
284
- .storeItem({
285
- key: initialKey.current,
286
- value,
287
- })
288
- .catch((error) => {
289
- console.error(`Failed to store item "${initialKey.current}":`, error);
290
- });
291
- }
292
- },
293
- [stateProvider, value$, getValue],
294
- );
295
-
296
- return [value, setValue];
297
- };
298
-
299
- export default useAppState;
@@ -1,59 +0,0 @@
1
- import { useLayoutEffect, useRef, useState, useSyncExternalStore } from 'react';
2
- import { BehaviorSubject } from 'rxjs';
3
- import { filter } from 'rxjs/operators';
4
-
5
- import type { EventModule } from '@equinor/fusion-framework-module-event';
6
- import { StateSyncEvent, type StateSyncEventType } from '@equinor/fusion-framework-module-state';
7
-
8
- import useAppModule from '../useAppModule';
9
-
10
- /**
11
- * Subscribes to the app's `state` module sync events (`onStateSync.status`,
12
- * `onStateSync.change`, `onStateSync.complete`, `onStateSync.error`) and returns the most
13
- * recent `limit` events, oldest first.
14
- *
15
- * Events are only dispatched while the state module's storage is configured for replication
16
- * (see `PouchDbSyncStorage`) - with the state module's default, local-only storage, this hook
17
- * returns an empty array.
18
- *
19
- * @param limit - Maximum number of most-recent sync events to retain.
20
- * @returns The most recent sync events, oldest first.
21
- *
22
- * @example
23
- * ```tsx
24
- * const events = useStateSyncEvents(20);
25
- * const lastEvent = events.at(-1);
26
- * ```
27
- *
28
- * @since 12.1.0
29
- */
30
- export const useStateSyncEvents = (limit: number): StateSyncEventType[] => {
31
- const eventProvider = useAppModule<EventModule>('event');
32
- const [event$] = useState(() => new BehaviorSubject<StateSyncEventType[]>([]));
33
-
34
- // Read through a ref so changing `limit` trims the log without resubscribing.
35
- const limitRef = useRef(limit);
36
- limitRef.current = limit;
37
-
38
- useLayoutEffect(() => {
39
- // Narrow the shared event stream down to the sync-related events this hook exposes.
40
- const subscription = eventProvider.event$.pipe(filter(StateSyncEvent.is)).subscribe((event) => {
41
- const next = [...event$.getValue(), event];
42
- // Clamp to 0 so `slice(-0)` (a no-op, unlike `slice(-1)`) can't retain the whole log.
43
- const limit = Math.max(0, limitRef.current);
44
- event$.next(limit === 0 ? [] : next.length > limit ? next.slice(-limit) : next);
45
- });
46
- return () => subscription.unsubscribe();
47
- }, [eventProvider, event$]);
48
-
49
- return useSyncExternalStore(
50
- (onChange) => {
51
- const subscription = event$.subscribe(onChange);
52
- return () => subscription.unsubscribe();
53
- },
54
- () => event$.getValue(),
55
- () => event$.getValue(),
56
- );
57
- };
58
-
59
- export default useStateSyncEvents;
@@ -1,59 +0,0 @@
1
- import { useMemo } from 'react';
2
- import { map } from 'rxjs/operators';
3
- import type { ConfigEnvironment } from '@equinor/fusion-framework-module-app';
4
- import { useCurrentApp } from '@equinor/fusion-framework-react/app';
5
- import {
6
- type ObservableStateReturnType,
7
- useObservableState,
8
- } from '@equinor/fusion-observable/react';
9
-
10
- /**
11
- * A React hook that provides access to the application's environment variables.
12
- *
13
- * This hook returns an observable state object that represents the current environment configuration.
14
- * The environment configuration is retrieved from the app module provided by the framework.
15
- *
16
- * @remarks This hook is only available when the app module is loaded (should be always for applications).
17
- * This hook in theory should always have a value if config was provided for the application, but is async by nature, hence the `complete` and `error` properties.
18
- *
19
- * @example
20
- * ```typescript
21
- * const MyComponent = () => {
22
- * const env = useAppEnvironmentVariables();
23
- * if(!env.complete) {
24
- * return <div>Loading environment variables...</div>;
25
- * }
26
- * if(env.error) {
27
- * return <div>Error loading environment variables</div>;
28
- * }
29
- * return <div>My environment variables: {JSON.stringify(env.value)}</div>;
30
- * }
31
- *
32
- * @template TEnvironmentVariables - The type of the environment variables. Defaults to `unknown`.
33
- * @returns An observable state object containing the current environment configuration.
34
- */
35
- export const useAppEnvironmentVariables = <
36
- TEnvironmentVariables extends ConfigEnvironment = ConfigEnvironment,
37
- >(): ObservableStateReturnType<TEnvironmentVariables> => {
38
- // Get the current app module instance from the framework
39
- const app = useCurrentApp<[], TEnvironmentVariables>().currentApp;
40
-
41
- // Ensure the app module is available before proceeding
42
- if (!app) {
43
- throw Error('Framework is missing app module');
44
- }
45
-
46
- const env$ = useMemo(() => {
47
- // Extract just the environment slice of the app's config for consumers
48
- return app.getConfig().pipe(
49
- map((config) => {
50
- return config.environment as TEnvironmentVariables;
51
- }),
52
- );
53
- }, [app]);
54
-
55
- // Return the observable state of the environment configuration
56
- return useObservableState(env$, {
57
- initial: app.config?.environment || ({} as TEnvironmentVariables),
58
- });
59
- };
@@ -1,45 +0,0 @@
1
- import type { AppModules, AppModulesInstance } from '@equinor/fusion-framework-app';
2
- import type {
3
- AnyModule,
4
- ModuleKey,
5
- ModuleType,
6
- ModuleTypes,
7
- } from '@equinor/fusion-framework-module';
8
-
9
- import { useAppModules } from './useAppModules';
10
-
11
- /**
12
- * React hook that retrieves a single module instance from the application scope.
13
- *
14
- * @template TType - The concrete module type (e.g. `ContextModule`). Pass
15
- * `unknown` to infer the type from the key.
16
- * @template TKey - The string key used to look up the module.
17
- * @param module - The key identifying the module to retrieve.
18
- * @returns The resolved module instance.
19
- * @throws If the requested module is not registered in the application scope.
20
- *
21
- * @example
22
- * ```tsx
23
- * const auth = useAppModule('auth');
24
- * auth.acquireAccessToken().then(console.log);
25
- * ```
26
- */
27
- export function useAppModule<
28
- TType extends AnyModule | unknown = unknown,
29
- TKey extends string = ModuleKey<ModuleTypes<AppModules<[TType]>>>,
30
- >(
31
- module: TKey,
32
- ): TType extends AnyModule
33
- ? ModuleType<TType>
34
- : AppModulesInstance[Extract<keyof AppModulesInstance, TKey>] {
35
- const appModule = useAppModules()[module as keyof AppModulesInstance];
36
- // Fail fast when the requested module was not registered on the app scope
37
- if (!appModule) {
38
- throw Error(`the requested module [${module}] is not included in the app scope`);
39
- }
40
- return appModule as TType extends AnyModule
41
- ? ModuleType<TType>
42
- : AppModulesInstance[Extract<keyof AppModulesInstance, TKey>];
43
- }
44
-
45
- export default useAppModule;
@@ -1,21 +0,0 @@
1
- import type { AppModules, AppModulesInstance } from '@equinor/fusion-framework-app';
2
- import type { AnyModule } from '@equinor/fusion-framework-module';
3
- import { useModules } from '@equinor/fusion-framework-react-module';
4
-
5
- /**
6
- * React hook that returns the full set of initialised application-scoped modules.
7
- *
8
- * @template T - Optional array of additional module types beyond the defaults.
9
- * @returns The {@link AppModulesInstance} containing all registered modules.
10
- *
11
- * @example
12
- * ```tsx
13
- * const modules = useAppModules();
14
- * console.log(Object.keys(modules));
15
- * ```
16
- */
17
- export const useAppModules = <
18
- T extends Array<AnyModule> | unknown = unknown,
19
- >(): AppModulesInstance<T> => useModules<AppModules<T>>() as AppModulesInstance<T>;
20
-
21
- export default useAppModules;
package/src/version.ts DELETED
@@ -1,2 +0,0 @@
1
- // Generated by genversion.
2
- export const version = '14.1.0';