diff --git a/packages/apps/esm-help-menu-app/src/help-menu/help-popup.component.tsx b/packages/apps/esm-help-menu-app/src/help-menu/help-popup.component.tsx index a1d54345c..389ef3d65 100644 --- a/packages/apps/esm-help-menu-app/src/help-menu/help-popup.component.tsx +++ b/packages/apps/esm-help-menu-app/src/help-menu/help-popup.component.tsx @@ -1,6 +1,6 @@ -import React, { useState } from 'react'; -import styles from './help-popup.styles.scss'; +import React from 'react'; import { ExtensionSlot } from '@openmrs/esm-framework'; +import styles from './help-popup.styles.scss'; export default function HelpMenuPopup() { return ( diff --git a/packages/apps/esm-help-menu-app/src/help-menu/help.component.tsx b/packages/apps/esm-help-menu-app/src/help-menu/help.component.tsx index d4deab080..1fc153f89 100644 --- a/packages/apps/esm-help-menu-app/src/help-menu/help.component.tsx +++ b/packages/apps/esm-help-menu-app/src/help-menu/help.component.tsx @@ -1,8 +1,8 @@ import React, { useState, useEffect, useRef } from 'react'; import classNames from 'classnames'; +import { Help } from '@carbon/react/icons'; import HelpMenuPopup from './help-popup.component'; import styles from './help.styles.scss'; -import { Help } from '@carbon/react/icons'; export default function HelpMenu() { const [helpMenuOpen, setHelpMenuOpen] = useState(false); @@ -14,7 +14,7 @@ export default function HelpMenu() { }; useEffect(() => { - const handleClickOutside = (event) => { + const handleClickOutside = (event: MouseEvent) => { if ( helpMenuButtonRef.current && !helpMenuButtonRef.current.contains(event.target) && @@ -24,12 +24,14 @@ export default function HelpMenu() { setHelpMenuOpen(false); } }; - document.addEventListener('click', handleClickOutside); + window.addEventListener(`mousedown`, handleClickOutside); + window.addEventListener(`touchstart`, handleClickOutside); return () => { - document.removeEventListener('click', handleClickOutside); + window.removeEventListener(`mousedown`, handleClickOutside); + window.removeEventListener(`touchstart`, handleClickOutside); }; - }, [helpMenuOpen]); + }, []); return ( <> diff --git a/packages/apps/esm-help-menu-app/src/root.component.test.tsx b/packages/apps/esm-help-menu-app/src/root.component.test.tsx index 818dd6210..53b9b423a 100644 --- a/packages/apps/esm-help-menu-app/src/root.component.test.tsx +++ b/packages/apps/esm-help-menu-app/src/root.component.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import Root from './root.component'; import { render } from '@testing-library/react'; +import Root from './root.component'; describe(``, () => { it(`renders without dying`, () => { diff --git a/packages/apps/esm-implementer-tools-app/jest.config.js b/packages/apps/esm-implementer-tools-app/jest.config.js index f877ef2d6..4ecbc9268 100644 --- a/packages/apps/esm-implementer-tools-app/jest.config.js +++ b/packages/apps/esm-implementer-tools-app/jest.config.js @@ -7,7 +7,8 @@ module.exports = { }, setupFilesAfterEnv: ['/setup-tests.ts'], moduleNameMapper: { - 'lodash-es': 'lodash', + '^lodash-es$': 'lodash', + '^lodash-es/(.*)$': 'lodash/$1', '\\.(s?css)$': 'identity-obj-proxy', '@openmrs/esm-framework': '@openmrs/esm-framework/mock.tsx', dexie: require.resolve('dexie'), diff --git a/packages/apps/esm-implementer-tools-app/src/configuration/configuration.test.tsx b/packages/apps/esm-implementer-tools-app/src/configuration/configuration.test.tsx index f605eb98c..b17a51352 100644 --- a/packages/apps/esm-implementer-tools-app/src/configuration/configuration.test.tsx +++ b/packages/apps/esm-implementer-tools-app/src/configuration/configuration.test.tsx @@ -9,7 +9,6 @@ import { useConceptLookup, useGetConceptByUuid } from './interactive-editor/valu const mockUseConceptLookup = useConceptLookup as jest.Mock; const mockUseGetConceptByUuid = useGetConceptByUuid as jest.Mock; -jest.mock('lodash-es/debounce', () => jest.fn((fn) => fn)); jest.mock('./interactive-editor/value-editors/concept-search.resource', () => ({ useConceptLookup: jest.fn().mockImplementation(() => ({ concepts: [], diff --git a/packages/apps/esm-offline-tools-app/jest.config.js b/packages/apps/esm-offline-tools-app/jest.config.js index 9b9e014d0..82eaa0682 100644 --- a/packages/apps/esm-offline-tools-app/jest.config.js +++ b/packages/apps/esm-offline-tools-app/jest.config.js @@ -3,6 +3,8 @@ module.exports = { '^.+\\.tsx?$': ['@swc/jest'], }, moduleNameMapper: { + '^lodash-es$': 'lodash', + '^lodash-es/(.*)$': 'lodash/$1', '\\.(s?css)$': 'identity-obj-proxy', }, setupFiles: ['/src/setup-tests.js'], diff --git a/packages/apps/esm-primary-navigation-app/src/components/navbar/navbar.component.tsx b/packages/apps/esm-primary-navigation-app/src/components/navbar/navbar.component.tsx index cdc57d425..41cd7e4c7 100644 --- a/packages/apps/esm-primary-navigation-app/src/components/navbar/navbar.component.tsx +++ b/packages/apps/esm-primary-navigation-app/src/components/navbar/navbar.component.tsx @@ -8,7 +8,7 @@ import { ExtensionSlot, ConfigurableLink, useSession, - useConnectedExtensions, + useAssignedExtensions, useConfig, CloseIcon, UserAvatarIcon, @@ -28,9 +28,9 @@ const HeaderItems: React.FC = () => { const config = useConfig(); const [activeHeaderPanel, setActiveHeaderPanel] = useState(null); const layout = useLayoutType(); - const navMenuItems = useConnectedExtensions('patient-chart-dashboard-slot').map((e) => e.id); - const appMenuItems = useConnectedExtensions('app-menu-slot'); - const userMenuItems = useConnectedExtensions('user-panel-slot'); + const navMenuItems = useAssignedExtensions('patient-chart-dashboard-slot').map((e) => e.id); + const appMenuItems = useAssignedExtensions('app-menu-slot'); + const userMenuItems = useAssignedExtensions('user-panel-slot'); const isActivePanel = useCallback((panelName: string) => activeHeaderPanel === panelName, [activeHeaderPanel]); const togglePanel = useCallback((panelName: string) => { diff --git a/packages/apps/esm-primary-navigation-app/src/root.component.test.tsx b/packages/apps/esm-primary-navigation-app/src/root.component.test.tsx index f1163317f..6fce3f460 100644 --- a/packages/apps/esm-primary-navigation-app/src/root.component.test.tsx +++ b/packages/apps/esm-primary-navigation-app/src/root.component.test.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { of } from 'rxjs'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { useConfig, useConnectedExtensions, useSession } from '@openmrs/esm-framework'; +import { useConfig, useAssignedExtensions, useSession } from '@openmrs/esm-framework'; import { isDesktop } from './utils'; import { mockUser } from '../__mocks__/mock-user'; import { mockSession } from '../__mocks__/mock-session'; @@ -13,13 +13,13 @@ const mockSessionObservable = of({ data: mockSession }); const mockIsDesktop = jest.mocked(isDesktop); const mockedUseConfig = useConfig as jest.Mock; -const mockedUseConnectedExtensions = useConnectedExtensions as jest.Mock; +const mockedUseAssignedExtensions = useAssignedExtensions as jest.Mock; const mockedUseSession = useSession as jest.Mock; mockedUseConfig.mockReturnValue({ logo: { src: null, alt: null, name: 'Mock EMR', link: 'Mock EMR' }, }); -mockedUseConnectedExtensions.mockReturnValue(['mock-extension']); +mockedUseAssignedExtensions.mockReturnValue(['mock-extension']); mockedUseSession.mockReturnValue(mockSession); jest.mock('./root.resource', () => ({ diff --git a/packages/framework/esm-api/src/openmrs-fetch.ts b/packages/framework/esm-api/src/openmrs-fetch.ts index c87d2862a..e4fc791db 100644 --- a/packages/framework/esm-api/src/openmrs-fetch.ts +++ b/packages/framework/esm-api/src/openmrs-fetch.ts @@ -1,6 +1,6 @@ /** @module @category API */ import { Observable } from 'rxjs'; -import isPlainObject from 'lodash-es/isPlainObject'; +import { isPlainObject } from 'lodash-es'; import { getConfig } from '@openmrs/esm-config'; import { navigate } from '@openmrs/esm-navigation'; import { clearHistory } from '@openmrs/esm-navigation/src/index'; diff --git a/packages/framework/esm-api/src/public.ts b/packages/framework/esm-api/src/public.ts index 7d46fdd20..9ea5cec30 100644 --- a/packages/framework/esm-api/src/public.ts +++ b/packages/framework/esm-api/src/public.ts @@ -1,8 +1,21 @@ export * from './types'; export * from './openmrs-fetch'; export * from './attachments'; - -export * from './shared-api-objects/current-user'; +export { + clearCurrentUser, + getCurrentUser, + getLoggedInUser, + getSessionStore, + getSessionLocation, + refetchCurrentUser, + setSessionLocation, + setUserLanguage, + setUserProperties, + userHasAccess, + type LoadedSessionStore, + type SessionStore, + type UnloadedSessionStore, +} from './shared-api-objects/current-user'; export * from './shared-api-objects/current-patient'; export * from './shared-api-objects/visit-utils'; export * from './shared-api-objects/visit-type'; diff --git a/packages/framework/esm-api/src/shared-api-objects/current-user.ts b/packages/framework/esm-api/src/shared-api-objects/current-user.ts index 46f7b5d64..482a790a3 100644 --- a/packages/framework/esm-api/src/shared-api-objects/current-user.ts +++ b/packages/framework/esm-api/src/shared-api-objects/current-user.ts @@ -1,7 +1,7 @@ /** @module @category API */ import { reportError } from '@openmrs/esm-error-handling'; import { createGlobalStore } from '@openmrs/esm-state'; -import isUndefined from 'lodash-es/isUndefined'; +import { isUndefined } from 'lodash-es'; import { Observable } from 'rxjs'; import { openmrsFetch, restBaseUrl, sessionEndpoint } from '../openmrs-fetch'; import type { LoggedInUser, SessionLocation, Privilege, Role, Session, FetchResponse } from '../types'; @@ -18,7 +18,8 @@ export type UnloadedSessionStore = { session: null; }; -const sessionStore = createGlobalStore('session', { +/** @internal */ +export const sessionStore = createGlobalStore('session', { loaded: false, session: null, }); diff --git a/packages/framework/esm-config/jest.config.js b/packages/framework/esm-config/jest.config.js index ec5109b35..e2d20e983 100644 --- a/packages/framework/esm-config/jest.config.js +++ b/packages/framework/esm-config/jest.config.js @@ -4,6 +4,7 @@ module.exports = { }, setupFiles: ['/src/setup-tests.js'], moduleNameMapper: { + '^lodash-es$': 'lodash', '@openmrs/esm-context': '/__mocks__/openmrs-esm-context.mock.tsx', '@openmrs/esm-globals': '/__mocks__/openmrs-esm-globals.mock.tsx', '@openmrs/esm-state': '@openmrs/esm-state/mock', diff --git a/packages/framework/esm-extensions/src/extensions.test.ts b/packages/framework/esm-extensions/src/extensions.test.ts index 803893b2d..a712a9e80 100644 --- a/packages/framework/esm-extensions/src/extensions.test.ts +++ b/packages/framework/esm-extensions/src/extensions.test.ts @@ -1,13 +1,11 @@ import { createGlobalStore } from '@openmrs/esm-state'; import { attach, registerExtensionSlot } from './extensions'; -const mockSessionStore = createGlobalStore('mock-session-store', { - loaded: false, - session: null, -}); - jest.mock('@openmrs/esm-api', () => ({ - getSessionStore: jest.fn(() => mockSessionStore), + sessionStore: createGlobalStore('mock-session-store', { + loaded: false, + session: null, + }), })); describe('extensions system', () => { diff --git a/packages/framework/esm-extensions/src/extensions.ts b/packages/framework/esm-extensions/src/extensions.ts index eee2eda31..976f9fb45 100644 --- a/packages/framework/esm-extensions/src/extensions.ts +++ b/packages/framework/esm-extensions/src/extensions.ts @@ -8,10 +8,11 @@ * - connected (computed from assigned using connectivity and online / offline) */ -import type { Session } from '@openmrs/esm-api'; -import { getSessionStore, userHasAccess } from '@openmrs/esm-api'; -import type { ExtensionsConfigStore, ExtensionSlotConfigObject, ExtensionSlotsConfigStore } from '@openmrs/esm-config'; +import { type Session, type SessionStore, sessionStore, userHasAccess } from '@openmrs/esm-api'; import { + type ExtensionsConfigStore, + type ExtensionSlotConfigObject, + type ExtensionSlotsConfigStore, getExtensionConfigFromStore, getExtensionsConfigStore, getExtensionSlotConfig, @@ -19,14 +20,18 @@ import { getExtensionSlotsConfigStore, } from '@openmrs/esm-config'; import { evaluateAsBoolean } from '@openmrs/esm-expression-evaluator'; -import { featureFlagsStore } from '@openmrs/esm-feature-flags'; +import { type FeatureFlagsStore, featureFlagsStore } from '@openmrs/esm-feature-flags'; +import { subscribeConnectivityChanged } from '@openmrs/esm-globals'; import { isOnline as isOnlineFn } from '@openmrs/esm-utils'; -import isEqual from 'lodash-es/isEqual'; -import isUndefined from 'lodash-es/isUndefined'; -import type { ExtensionSlotState, AssignedExtension, ConnectedExtension } from '.'; -import { getExtensionInternalStore, checkStatusFor } from '.'; -import type { ExtensionRegistration, ExtensionSlotInfo, ExtensionInternalStore } from './store'; -import { getExtensionStore, updateInternalExtensionStore } from './store'; +import { isEqual } from 'lodash-es'; +import { type AssignedExtension, checkStatusFor, type ExtensionSlotState, getExtensionInternalStore } from '.'; +import { + type ExtensionRegistration, + type ExtensionSlotInfo, + type ExtensionInternalStore, + getExtensionStore, + updateInternalExtensionStore, +} from './store'; const extensionInternalStore = getExtensionInternalStore(); const extensionStore = getExtensionStore(); @@ -38,8 +43,16 @@ function updateExtensionOutputStore( internalState: ExtensionInternalStore, extensionSlotConfigs: ExtensionSlotsConfigStore, extensionsConfigStore: ExtensionsConfigStore, + featureFlagStore: FeatureFlagsStore, + sessionStore: SessionStore, ) { const slots: Record = {}; + + const isOnline = isOnlineFn(); + const enabledFeatureFlags = Object.entries(featureFlagStore.flags) + .filter(([, { enabled }]) => enabled) + .map(([name]) => name); + for (let [slotName, slot] of Object.entries(internalState.slots)) { const { config } = getExtensionSlotConfigFromStore(extensionSlotConfigs, slot.name); const assignedExtensions = getAssignedExtensionsFromSlotData( @@ -47,26 +60,81 @@ function updateExtensionOutputStore( internalState, config, extensionsConfigStore, + enabledFeatureFlags, + isOnline, + sessionStore.session, ); slots[slotName] = { moduleName: slot.moduleName, assignedExtensions }; } + if (!isEqual(extensionStore.getState().slots, slots)) { extensionStore.setState({ slots }); } } extensionInternalStore.subscribe((internalStore) => { - updateExtensionOutputStore(internalStore, slotsConfigStore.getState(), extensionsConfigStore.getState()); + updateExtensionOutputStore( + internalStore, + slotsConfigStore.getState(), + extensionsConfigStore.getState(), + featureFlagsStore.getState(), + sessionStore.getState(), + ); }); slotsConfigStore.subscribe((slotConfigs) => { - updateExtensionOutputStore(extensionInternalStore.getState(), slotConfigs, extensionsConfigStore.getState()); + updateExtensionOutputStore( + extensionInternalStore.getState(), + slotConfigs, + extensionsConfigStore.getState(), + featureFlagsStore.getState(), + sessionStore.getState(), + ); }); extensionsConfigStore.subscribe((extensionConfigs) => { - updateExtensionOutputStore(extensionInternalStore.getState(), slotsConfigStore.getState(), extensionConfigs); + updateExtensionOutputStore( + extensionInternalStore.getState(), + slotsConfigStore.getState(), + extensionConfigs, + featureFlagsStore.getState(), + sessionStore.getState(), + ); +}); + +featureFlagsStore.subscribe((featureFlagStore) => { + updateExtensionOutputStore( + extensionInternalStore.getState(), + slotsConfigStore.getState(), + extensionsConfigStore.getState(), + featureFlagStore, + sessionStore.getState(), + ); }); +sessionStore.subscribe((session) => { + updateExtensionOutputStore( + extensionInternalStore.getState(), + slotsConfigStore.getState(), + extensionsConfigStore.getState(), + featureFlagsStore.getState(), + session, + ); +}); + +function updateOutputStoreToCurrent() { + updateExtensionOutputStore( + extensionInternalStore.getState(), + slotsConfigStore.getState(), + extensionsConfigStore.getState(), + featureFlagsStore.getState(), + sessionStore.getState(), + ); +} + +updateOutputStoreToCurrent(); +subscribeConnectivityChanged(updateOutputStoreToCurrent); + function createNewExtensionSlotInfo(slotName: string, moduleName?: string): ExtensionSlotInfo { return { moduleName, @@ -248,43 +316,18 @@ function getOrder( } } -/** - * Filters a list of extensions according to whether they support the - * current connectivity status. - * - * @param assignedExtensions The list of extensions to filter. - * @param online Whether the app is currently online. If `null`, uses `navigator.onLine`. - * @param enabledFeatureFlags The names of all enabled feature flags. If `null`, looks - * up the feature flags using the feature flags API. - * @returns A list of extensions that should be rendered - */ -export function getConnectedExtensions( - assignedExtensions: Array, - online: boolean | null = null, - enabledFeatureFlags: Array | null = null, -): Array { - const isOnline = isOnlineFn(online ?? undefined); - const featureFlags = - enabledFeatureFlags ?? - Object.entries(featureFlagsStore.getState().flags) - .filter(([, { enabled }]) => enabled) - .map(([name]) => name); - - return assignedExtensions - .filter((e) => (window.offlineEnabled ? checkStatusFor(isOnline, e.online, e.offline) : true)) - .filter((e) => e.featureFlag === undefined || featureFlags?.includes(e.featureFlag)); -} - function getAssignedExtensionsFromSlotData( slotName: string, internalState: ExtensionInternalStore, config: ExtensionSlotConfigObject, extensionConfigStoreState: ExtensionsConfigStore, + enabledFeatureFlags: Array, + isOnline: boolean, + session: Session | null, ): Array { const attachedIds = internalState.slots[slotName].attachedIds; const assignedIds = calculateAssignedIds(config, attachedIds); const extensions: Array = []; - let session: Session | undefined = undefined; for (let id of assignedIds) { const { config: extensionConfig } = getExtensionConfigFromStore(extensionConfigStoreState, slotName, id); @@ -298,10 +341,6 @@ function getAssignedExtensionsFromSlotData( requiredPrivileges && (typeof requiredPrivileges === 'string' || (Array.isArray(requiredPrivileges) && requiredPrivileges.length > 0)) ) { - if (isUndefined(session)) { - session = getSessionStore().getState().session ?? undefined; - } - if (!session?.user) { continue; } @@ -313,12 +352,8 @@ function getAssignedExtensionsFromSlotData( const displayConditionExpression = extensionConfig?.['Display conditions']?.expression ?? null; if (displayConditionExpression !== null) { - if (isUndefined(session)) { - session = getSessionStore().getState().session ?? undefined; - } - try { - if (!evaluateAsBoolean(displayConditionExpression, { session: session ?? null })) { + if (!evaluateAsBoolean(displayConditionExpression, { session })) { continue; } } catch (e) { @@ -328,6 +363,14 @@ function getAssignedExtensionsFromSlotData( } } + if (extension.featureFlag && !enabledFeatureFlags.includes(extension.featureFlag)) { + continue; + } + + if (window.offlineEnabled && !checkStatusFor(isOnline, extension.online, extension.offline)) { + continue; + } + extensions.push({ id, name, @@ -354,7 +397,22 @@ export function getAssignedExtensions(slotName: string): Array enabled) + .map(([name]) => name); + + return getAssignedExtensionsFromSlotData( + slotName, + internalState, + slotConfig, + extensionStoreState, + enabledFeatureFlags, + isOnline, + sessionState.session, + ); } function calculateAssignedIds(config: ExtensionSlotConfigObject, attachedIds: Array) { diff --git a/packages/framework/esm-extensions/src/public.ts b/packages/framework/esm-extensions/src/public.ts index 5e6a91a06..8b378548c 100644 --- a/packages/framework/esm-extensions/src/public.ts +++ b/packages/framework/esm-extensions/src/public.ts @@ -4,7 +4,6 @@ export { attach, detach, detachAll, - getConnectedExtensions, getAssignedExtensions, registerExtensionSlot, } from './extensions'; diff --git a/packages/framework/esm-extensions/src/store.ts b/packages/framework/esm-extensions/src/store.ts index 9b7955eab..6fb5a4948 100644 --- a/packages/framework/esm-extensions/src/store.ts +++ b/packages/framework/esm-extensions/src/store.ts @@ -1,24 +1,24 @@ /** @module @category Extension */ -import isEqual from 'lodash-es/isEqual'; +import { isEqual } from 'lodash-es'; import type { ConfigExtensionStoreElement, ConfigObject, ExtensionSlotConfigObject } from '@openmrs/esm-config'; import { configExtensionStore } from '@openmrs/esm-config'; import { createGlobalStore, getGlobalStore } from '@openmrs/esm-state'; -import type { LifeCycles } from 'single-spa'; +import { type LifeCycles } from 'single-spa'; export interface ExtensionMeta { [_: string]: any; } export interface ExtensionRegistration { - name: string; + readonly name: string; load(): Promise<{ default?: LifeCycles } & LifeCycles>; - moduleName: string; - meta: ExtensionMeta; - order?: number; - online?: boolean; - offline?: boolean; - privileges?: string | Array; - featureFlag?: string; + readonly moduleName: string; + readonly meta: Readonly; + readonly order?: number; + readonly online?: boolean; + readonly offline?: boolean; + readonly privileges?: string | Array; + readonly featureFlag?: string; } export interface ExtensionInfo extends ExtensionRegistration { @@ -70,24 +70,25 @@ export interface ExtensionSlotState { } export interface AssignedExtension { - id: string; - name: string; - moduleName: string; - meta: ExtensionMeta; + readonly id: string; + readonly name: string; + readonly moduleName: string; + readonly meta: Readonly; /** The extension's config. Note that this will be `null` until the slot is mounted. */ - config: ConfigObject | null; - online?: boolean | object; - offline?: boolean | object; - featureFlag?: string; + readonly config: Readonly | null; + readonly online?: boolean | object; + readonly offline?: boolean | object; + readonly featureFlag?: string; } +/** @deprecated replaced with AssignedExtension */ export interface ConnectedExtension { - id: string; - name: string; - moduleName: string; - meta: ExtensionMeta; + readonly id: string; + readonly name: string; + readonly moduleName: string; + readonly meta: Readonly; /** The extension's config. Note that this will be `null` until the slot is mounted. */ - config: ConfigObject | null; + readonly config: Readonly | null; } const extensionInternalStore = createGlobalStore('extensionsInternal', { @@ -148,6 +149,7 @@ function updateConfigExtensionStore(extensionState: ExtensionInternalStore) { }); } } + if (!isEqual(configExtensionStore.getState().mountedExtensions, configExtensionRecords)) { configExtensionStore.setState({ mountedExtensions: configExtensionRecords, diff --git a/packages/framework/esm-framework/docs/API.md b/packages/framework/esm-framework/docs/API.md index 79f6fdfb6..415a1510b 100644 --- a/packages/framework/esm-framework/docs/API.md +++ b/packages/framework/esm-framework/docs/API.md @@ -112,7 +112,6 @@ - [detach](API.md#detach) - [detachAll](API.md#detachall) - [getAssignedExtensions](API.md#getassignedextensions) -- [getConnectedExtensions](API.md#getconnectedextensions) - [getExtensionNameFromId](API.md#getextensionnamefromid) - [getExtensionStore](API.md#getextensionstore) - [renderExtension](API.md#renderextension) @@ -120,6 +119,7 @@ - [useAssignedExtensions](API.md#useassignedextensions) - [useConnectedExtensions](API.md#useconnectedextensions) - [useExtensionSlotMeta](API.md#useextensionslotmeta) +- [useExtensionSlotStore](API.md#useextensionslotstore) - [useExtensionStore](API.md#useextensionstore) - [useRenderableExtensions](API.md#userenderableextensions) @@ -422,17 +422,17 @@ ___ ### ExtensionProps -Ƭ **ExtensionProps**: { `state?`: `Record`<`string`, `any`\> ; `wrap?`: (`slot`: `ReactNode`, `extension`: [`ExtensionData`](interfaces/ExtensionData.md)) => ``null`` \| `ReactElement`<`any`, `any`\> } & `Omit`<`React.HTMLAttributes`<`HTMLDivElement`\>, ``"children"``\> & { `children?`: `React.ReactNode` \| (`slot`: `React.ReactNode`, `extension?`: [`ExtensionData`](interfaces/ExtensionData.md)) => `React.ReactNode` } +Ƭ **ExtensionProps**: `React.HTMLAttributes`<`HTMLDivElement`\> & { `state?`: `Record`<`string`, `unknown`\> } #### Defined in -[packages/framework/esm-react-utils/src/Extension.tsx:8](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/Extension.tsx#L8) +[packages/framework/esm-react-utils/src/Extension.tsx:7](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/Extension.tsx#L7) ___ ### ExtensionSlotProps -Ƭ **ExtensionSlotProps**: [`OldExtensionSlotBaseProps`](interfaces/OldExtensionSlotBaseProps.md) \| [`ExtensionSlotBaseProps`](interfaces/ExtensionSlotBaseProps.md) & `Omit`<`React.HTMLAttributes`<`HTMLDivElement`\>, ``"children"``\> & { `children?`: `React.ReactNode` \| (`extension`: [`ConnectedExtension`](interfaces/ConnectedExtension.md)) => `React.ReactNode` } +Ƭ **ExtensionSlotProps**: [`OldExtensionSlotBaseProps`](interfaces/OldExtensionSlotBaseProps.md) \| [`ExtensionSlotBaseProps`](interfaces/ExtensionSlotBaseProps.md) & `Omit`<`React.HTMLAttributes`<`HTMLDivElement`\>, ``"children"``\> & { `children?`: `React.ReactNode` \| (`extension`: [`AssignedExtension`](interfaces/AssignedExtension.md), `state?`: `Record`<`string`, `unknown`\>) => `React.ReactNode` } #### Defined in @@ -1098,7 +1098,7 @@ and *must* only be used once within that ``. #### Defined in -[packages/framework/esm-react-utils/src/Extension.tsx:25](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/Extension.tsx#L25) +[packages/framework/esm-react-utils/src/Extension.tsx:20](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/Extension.tsx#L20) ___ @@ -2175,7 +2175,7 @@ ___ #### Defined in -[packages/framework/esm-api/src/shared-api-objects/current-user.ts:168](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-api/src/shared-api-objects/current-user.ts#L168) +[packages/framework/esm-api/src/shared-api-objects/current-user.ts:169](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-api/src/shared-api-objects/current-user.ts#L169) ___ @@ -2327,7 +2327,7 @@ leak and source of bugs. #### Defined in -[packages/framework/esm-api/src/shared-api-objects/current-user.ts:65](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-api/src/shared-api-objects/current-user.ts#L65) +[packages/framework/esm-api/src/shared-api-objects/current-user.ts:66](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-api/src/shared-api-objects/current-user.ts#L66) ▸ **getCurrentUser**(`opts`): `Observable`<[`Session`](interfaces/Session.md)\> @@ -2344,7 +2344,7 @@ leak and source of bugs. #### Defined in -[packages/framework/esm-api/src/shared-api-objects/current-user.ts:66](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-api/src/shared-api-objects/current-user.ts#L66) +[packages/framework/esm-api/src/shared-api-objects/current-user.ts:67](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-api/src/shared-api-objects/current-user.ts#L67) ▸ **getCurrentUser**(`opts`): `Observable`<[`LoggedInUser`](interfaces/LoggedInUser.md)\> @@ -2361,7 +2361,7 @@ leak and source of bugs. #### Defined in -[packages/framework/esm-api/src/shared-api-objects/current-user.ts:67](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-api/src/shared-api-objects/current-user.ts#L67) +[packages/framework/esm-api/src/shared-api-objects/current-user.ts:68](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-api/src/shared-api-objects/current-user.ts#L68) ___ @@ -2395,7 +2395,7 @@ ___ #### Defined in -[packages/framework/esm-api/src/shared-api-objects/current-user.ts:192](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-api/src/shared-api-objects/current-user.ts#L192) +[packages/framework/esm-api/src/shared-api-objects/current-user.ts:193](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-api/src/shared-api-objects/current-user.ts#L193) ___ @@ -2409,7 +2409,7 @@ ___ #### Defined in -[packages/framework/esm-api/src/shared-api-objects/current-user.ts:210](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-api/src/shared-api-objects/current-user.ts#L210) +[packages/framework/esm-api/src/shared-api-objects/current-user.ts:211](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-api/src/shared-api-objects/current-user.ts#L211) ___ @@ -2423,7 +2423,7 @@ ___ #### Defined in -[packages/framework/esm-api/src/shared-api-objects/current-user.ts:92](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-api/src/shared-api-objects/current-user.ts#L92) +[packages/framework/esm-api/src/shared-api-objects/current-user.ts:93](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-api/src/shared-api-objects/current-user.ts#L93) ___ @@ -2653,7 +2653,7 @@ refetchCurrentUser() #### Defined in -[packages/framework/esm-api/src/shared-api-objects/current-user.ts:154](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-api/src/shared-api-objects/current-user.ts#L154) +[packages/framework/esm-api/src/shared-api-objects/current-user.ts:155](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-api/src/shared-api-objects/current-user.ts#L155) ___ @@ -2716,7 +2716,7 @@ ___ #### Defined in -[packages/framework/esm-api/src/shared-api-objects/current-user.ts:219](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-api/src/shared-api-objects/current-user.ts#L219) +[packages/framework/esm-api/src/shared-api-objects/current-user.ts:220](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-api/src/shared-api-objects/current-user.ts#L220) ___ @@ -2736,7 +2736,7 @@ ___ #### Defined in -[packages/framework/esm-api/src/shared-api-objects/current-user.ts:115](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-api/src/shared-api-objects/current-user.ts#L115) +[packages/framework/esm-api/src/shared-api-objects/current-user.ts:116](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-api/src/shared-api-objects/current-user.ts#L116) ___ @@ -2758,7 +2758,7 @@ ___ #### Defined in -[packages/framework/esm-api/src/shared-api-objects/current-user.ts:232](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-api/src/shared-api-objects/current-user.ts#L232) +[packages/framework/esm-api/src/shared-api-objects/current-user.ts:233](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-api/src/shared-api-objects/current-user.ts#L233) ___ @@ -3017,7 +3017,7 @@ ___ #### Defined in -[packages/framework/esm-api/src/shared-api-objects/current-user.ts:175](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-api/src/shared-api-objects/current-user.ts#L175) +[packages/framework/esm-api/src/shared-api-objects/current-user.ts:176](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-api/src/shared-api-objects/current-user.ts#L176) ___ @@ -4269,7 +4269,7 @@ writing a module for a specific implementation. #### Defined in -[packages/framework/esm-extensions/src/extensions.ts:144](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-extensions/src/extensions.ts#L144) +[packages/framework/esm-extensions/src/extensions.ts:212](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-extensions/src/extensions.ts#L212) ___ @@ -4292,7 +4292,7 @@ ___ #### Defined in -[packages/framework/esm-extensions/src/extensions.ts:177](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-extensions/src/extensions.ts#L177) +[packages/framework/esm-extensions/src/extensions.ts:245](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-extensions/src/extensions.ts#L245) ___ @@ -4314,7 +4314,7 @@ ___ #### Defined in -[packages/framework/esm-extensions/src/extensions.ts:201](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-extensions/src/extensions.ts#L201) +[packages/framework/esm-extensions/src/extensions.ts:269](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-extensions/src/extensions.ts#L269) ___ @@ -4338,34 +4338,7 @@ An array of extensions assigned to the named slot #### Defined in -[packages/framework/esm-extensions/src/extensions.ts:353](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-extensions/src/extensions.ts#L353) - -___ - -### getConnectedExtensions - -▸ **getConnectedExtensions**(`assignedExtensions`, `online?`, `enabledFeatureFlags?`): [`ConnectedExtension`](interfaces/ConnectedExtension.md)[] - -Filters a list of extensions according to whether they support the -current connectivity status. - -#### Parameters - -| Name | Type | Default value | Description | -| :------ | :------ | :------ | :------ | -| `assignedExtensions` | [`AssignedExtension`](interfaces/AssignedExtension.md)[] | `undefined` | The list of extensions to filter. | -| `online` | ``null`` \| `boolean` | `null` | Whether the app is currently online. If `null`, uses `navigator.onLine`. | -| `enabledFeatureFlags` | ``null`` \| `string`[] | `null` | The names of all enabled feature flags. If `null`, looks up the feature flags using the feature flags API. | - -#### Returns - -[`ConnectedExtension`](interfaces/ConnectedExtension.md)[] - -A list of extensions that should be rendered - -#### Defined in - -[packages/framework/esm-extensions/src/extensions.ts:261](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-extensions/src/extensions.ts#L261) +[packages/framework/esm-extensions/src/extensions.ts:396](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-extensions/src/extensions.ts#L396) ___ @@ -4397,7 +4370,7 @@ getExtensionNameFromId("baz") #### Defined in -[packages/framework/esm-extensions/src/extensions.ts:92](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-extensions/src/extensions.ts#L92) +[packages/framework/esm-extensions/src/extensions.ts:160](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-extensions/src/extensions.ts#L160) ___ @@ -4414,7 +4387,7 @@ state of the extension system. #### Defined in -[packages/framework/esm-extensions/src/store.ts:124](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-extensions/src/store.ts#L124) +[packages/framework/esm-extensions/src/store.ts:125](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-extensions/src/store.ts#L125) ___ @@ -4477,7 +4450,6 @@ ___ ▸ **useAssignedExtensions**(`slotName`): [`AssignedExtension`](interfaces/AssignedExtension.md)[] Gets the assigned extensions for a given extension slot name. -Does not consider if offline or online. #### Parameters @@ -4491,7 +4463,7 @@ Does not consider if offline or online. #### Defined in -[packages/framework/esm-react-utils/src/useAssignedExtensions.ts:10](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useAssignedExtensions.ts#L10) +[packages/framework/esm-react-utils/src/useAssignedExtensions.ts:8](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useAssignedExtensions.ts#L8) ___ @@ -4500,7 +4472,8 @@ ___ ▸ **useConnectedExtensions**(`slotName`): [`ConnectedExtension`](interfaces/ConnectedExtension.md)[] Gets the assigned extension for a given extension slot name. -Considers if offline or online, and what feature flags are enabled. + +**`deprecated`** Use useAssignedExtensions instead #### Parameters @@ -4514,7 +4487,7 @@ Considers if offline or online, and what feature flags are enabled. #### Defined in -[packages/framework/esm-react-utils/src/useConnectedExtensions.ts:15](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useConnectedExtensions.ts#L15) +[packages/framework/esm-react-utils/src/useConnectedExtensions.ts:10](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useConnectedExtensions.ts#L10) ___ @@ -4546,6 +4519,26 @@ Extract meta data from all extension for a given extension slot. ___ +### useExtensionSlotStore + +▸ **useExtensionSlotStore**(`slot`): [`ExtensionSlotState`](interfaces/ExtensionSlotState.md) + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `slot` | `string` | + +#### Returns + +[`ExtensionSlotState`](interfaces/ExtensionSlotState.md) + +#### Defined in + +[packages/framework/esm-react-utils/src/useExtensionSlotStore.ts:5](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useExtensionSlotStore.ts#L5) + +___ + ### useExtensionStore ▸ **useExtensionStore**(): `T` @@ -4556,7 +4549,7 @@ ___ #### Defined in -[packages/framework/esm-react-utils/src/useExtensionStore.ts:6](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useExtensionStore.ts#L6) +[packages/framework/esm-react-utils/src/useExtensionStore.ts:5](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useExtensionStore.ts#L5) ▸ **useExtensionStore**(`actions`): `T` & [`BoundActions`](API.md#boundactions) @@ -4572,7 +4565,7 @@ ___ #### Defined in -[packages/framework/esm-react-utils/src/useExtensionStore.ts:6](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useExtensionStore.ts#L6) +[packages/framework/esm-react-utils/src/useExtensionStore.ts:5](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useExtensionStore.ts#L5) ▸ **useExtensionStore**(`actions?`): `T` & [`BoundActions`](API.md#boundactions) @@ -4588,7 +4581,7 @@ ___ #### Defined in -[packages/framework/esm-react-utils/src/useExtensionStore.ts:6](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useExtensionStore.ts#L6) +[packages/framework/esm-react-utils/src/useExtensionStore.ts:5](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useExtensionStore.ts#L5) ___ @@ -5808,7 +5801,7 @@ This component also provides everything needed for workspace notifications to be #### Defined in -[packages/framework/esm-styleguide/src/workspaces/container/workspace-container.component.tsx:68](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/workspaces/container/workspace-container.component.tsx#L68) +[packages/framework/esm-styleguide/src/workspaces/container/workspace-container.component.tsx:67](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/workspaces/container/workspace-container.component.tsx#L67) ___ @@ -6309,7 +6302,7 @@ The newly created store. #### Defined in -[packages/framework/esm-state/src/state.ts:29](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-state/src/state.ts#L29) +[packages/framework/esm-state/src/state.ts:30](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-state/src/state.ts#L30) ___ @@ -6368,7 +6361,7 @@ custom hook for a specific store. #### Defined in -[packages/framework/esm-react-utils/src/useStore.ts:60](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useStore.ts#L60) +[packages/framework/esm-react-utils/src/useStore.ts:63](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useStore.ts#L63) ___ @@ -6400,12 +6393,44 @@ The found or newly created store. #### Defined in -[packages/framework/esm-state/src/state.ts:91](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-state/src/state.ts#L91) +[packages/framework/esm-state/src/state.ts:92](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-state/src/state.ts#L92) ___ ### subscribeTo +▸ **subscribeTo**<`T`, `U`\>(`store`, `handle`): () => `void` + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `T` | `T` | +| `U` | `T` | + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `store` | `StoreApi`<`T`\> | +| `handle` | (`state`: `T`) => `void` | + +#### Returns + +`fn` + +▸ (): `void` + +**`category`** Store + +##### Returns + +`void` + +#### Defined in + +[packages/framework/esm-state/src/state.ts:109](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-state/src/state.ts#L109) + ▸ **subscribeTo**<`T`, `U`\>(`store`, `select`, `handle`): () => `void` #### Type parameters @@ -6429,13 +6454,15 @@ ___ ▸ (): `void` +**`category`** Store + ##### Returns `void` #### Defined in -[packages/framework/esm-state/src/state.ts:106](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-state/src/state.ts#L106) +[packages/framework/esm-state/src/state.ts:110](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-state/src/state.ts#L110) ___ @@ -6462,7 +6489,7 @@ ___ #### Defined in -[packages/framework/esm-react-utils/src/useStore.ts:33](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useStore.ts#L33) +[packages/framework/esm-react-utils/src/useStore.ts:36](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useStore.ts#L36) ▸ **useStore**<`T`, `U`\>(`store`, `select`): `U` @@ -6486,7 +6513,7 @@ ___ #### Defined in -[packages/framework/esm-react-utils/src/useStore.ts:34](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useStore.ts#L34) +[packages/framework/esm-react-utils/src/useStore.ts:37](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useStore.ts#L37) ▸ **useStore**<`T`, `U`\>(`store`, `select`, `actions`): `T` & [`BoundActions`](API.md#boundactions) @@ -6511,7 +6538,7 @@ ___ #### Defined in -[packages/framework/esm-react-utils/src/useStore.ts:35](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useStore.ts#L35) +[packages/framework/esm-react-utils/src/useStore.ts:38](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useStore.ts#L38) ▸ **useStore**<`T`, `U`\>(`store`, `select`, `actions`): `U` & [`BoundActions`](API.md#boundactions) @@ -6536,7 +6563,7 @@ ___ #### Defined in -[packages/framework/esm-react-utils/src/useStore.ts:36](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useStore.ts#L36) +[packages/framework/esm-react-utils/src/useStore.ts:39](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useStore.ts#L39) ___ @@ -6563,7 +6590,7 @@ ___ #### Defined in -[packages/framework/esm-react-utils/src/useStore.ts:52](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useStore.ts#L52) +[packages/framework/esm-react-utils/src/useStore.ts:55](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-react-utils/src/useStore.ts#L55) ___ diff --git a/packages/framework/esm-framework/docs/interfaces/AssignedExtension.md b/packages/framework/esm-framework/docs/interfaces/AssignedExtension.md index 5a1849149..2bc9a79d4 100644 --- a/packages/framework/esm-framework/docs/interfaces/AssignedExtension.md +++ b/packages/framework/esm-framework/docs/interfaces/AssignedExtension.md @@ -19,7 +19,7 @@ ### config -• **config**: ``null`` \| [`ConfigObject`](ConfigObject.md) +• `Readonly` **config**: ``null`` \| `Readonly`<[`ConfigObject`](ConfigObject.md)\> The extension's config. Note that this will be `null` until the slot is mounted. @@ -31,7 +31,7 @@ ___ ### featureFlag -• `Optional` **featureFlag**: `string` +• `Optional` `Readonly` **featureFlag**: `string` #### Defined in @@ -41,7 +41,7 @@ ___ ### id -• **id**: `string` +• `Readonly` **id**: `string` #### Defined in @@ -51,7 +51,7 @@ ___ ### meta -• **meta**: [`ExtensionMeta`](ExtensionMeta.md) +• `Readonly` **meta**: `Readonly`<[`ExtensionMeta`](ExtensionMeta.md)\> #### Defined in @@ -61,7 +61,7 @@ ___ ### moduleName -• **moduleName**: `string` +• `Readonly` **moduleName**: `string` #### Defined in @@ -71,7 +71,7 @@ ___ ### name -• **name**: `string` +• `Readonly` **name**: `string` #### Defined in @@ -81,7 +81,7 @@ ___ ### offline -• `Optional` **offline**: `boolean` \| `object` +• `Optional` `Readonly` **offline**: `boolean` \| `object` #### Defined in @@ -91,7 +91,7 @@ ___ ### online -• `Optional` **online**: `boolean` \| `object` +• `Optional` `Readonly` **online**: `boolean` \| `object` #### Defined in diff --git a/packages/framework/esm-framework/docs/interfaces/ConnectedExtension.md b/packages/framework/esm-framework/docs/interfaces/ConnectedExtension.md index e649272ef..91232cbf8 100644 --- a/packages/framework/esm-framework/docs/interfaces/ConnectedExtension.md +++ b/packages/framework/esm-framework/docs/interfaces/ConnectedExtension.md @@ -2,6 +2,8 @@ # Interface: ConnectedExtension +**`deprecated`** replaced with AssignedExtension + ## Table of contents ### Extension Properties @@ -16,50 +18,50 @@ ### config -• **config**: ``null`` \| [`ConfigObject`](ConfigObject.md) +• `Readonly` **config**: ``null`` \| `Readonly`<[`ConfigObject`](ConfigObject.md)\> The extension's config. Note that this will be `null` until the slot is mounted. #### Defined in -[packages/framework/esm-extensions/src/store.ts:90](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-extensions/src/store.ts#L90) +[packages/framework/esm-extensions/src/store.ts:91](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-extensions/src/store.ts#L91) ___ ### id -• **id**: `string` +• `Readonly` **id**: `string` #### Defined in -[packages/framework/esm-extensions/src/store.ts:85](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-extensions/src/store.ts#L85) +[packages/framework/esm-extensions/src/store.ts:86](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-extensions/src/store.ts#L86) ___ ### meta -• **meta**: [`ExtensionMeta`](ExtensionMeta.md) +• `Readonly` **meta**: `Readonly`<[`ExtensionMeta`](ExtensionMeta.md)\> #### Defined in -[packages/framework/esm-extensions/src/store.ts:88](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-extensions/src/store.ts#L88) +[packages/framework/esm-extensions/src/store.ts:89](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-extensions/src/store.ts#L89) ___ ### moduleName -• **moduleName**: `string` +• `Readonly` **moduleName**: `string` #### Defined in -[packages/framework/esm-extensions/src/store.ts:87](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-extensions/src/store.ts#L87) +[packages/framework/esm-extensions/src/store.ts:88](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-extensions/src/store.ts#L88) ___ ### name -• **name**: `string` +• `Readonly` **name**: `string` #### Defined in -[packages/framework/esm-extensions/src/store.ts:86](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-extensions/src/store.ts#L86) +[packages/framework/esm-extensions/src/store.ts:87](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-extensions/src/store.ts#L87) diff --git a/packages/framework/esm-framework/docs/interfaces/ExtensionRegistration.md b/packages/framework/esm-framework/docs/interfaces/ExtensionRegistration.md index a5a8829cd..90c4d0f38 100644 --- a/packages/framework/esm-framework/docs/interfaces/ExtensionRegistration.md +++ b/packages/framework/esm-framework/docs/interfaces/ExtensionRegistration.md @@ -23,7 +23,7 @@ ### featureFlag -• `Optional` **featureFlag**: `string` +• `Optional` `Readonly` **featureFlag**: `string` #### Defined in @@ -33,7 +33,7 @@ ___ ### meta -• **meta**: [`ExtensionMeta`](ExtensionMeta.md) +• `Readonly` **meta**: `Readonly`<[`ExtensionMeta`](ExtensionMeta.md)\> #### Defined in @@ -43,7 +43,7 @@ ___ ### moduleName -• **moduleName**: `string` +• `Readonly` **moduleName**: `string` #### Defined in @@ -53,7 +53,7 @@ ___ ### name -• **name**: `string` +• `Readonly` **name**: `string` #### Defined in @@ -63,7 +63,7 @@ ___ ### offline -• `Optional` **offline**: `boolean` +• `Optional` `Readonly` **offline**: `boolean` #### Defined in @@ -73,7 +73,7 @@ ___ ### online -• `Optional` **online**: `boolean` +• `Optional` `Readonly` **online**: `boolean` #### Defined in @@ -83,7 +83,7 @@ ___ ### order -• `Optional` **order**: `number` +• `Optional` `Readonly` **order**: `number` #### Defined in @@ -93,7 +93,7 @@ ___ ### privileges -• `Optional` **privileges**: `string` \| `string`[] +• `Optional` `Readonly` **privileges**: `string` \| `string`[] #### Defined in diff --git a/packages/framework/esm-framework/docs/interfaces/ExtensionSlotBaseProps.md b/packages/framework/esm-framework/docs/interfaces/ExtensionSlotBaseProps.md index 0614895ba..2f1051faf 100644 --- a/packages/framework/esm-framework/docs/interfaces/ExtensionSlotBaseProps.md +++ b/packages/framework/esm-framework/docs/interfaces/ExtensionSlotBaseProps.md @@ -40,7 +40,7 @@ ___ ### state -• `Optional` **state**: `Record`<`string`, `any`\> +• `Optional` **state**: `Record`<`string`, `unknown`\> #### Defined in @@ -50,17 +50,17 @@ ___ ### select -▸ `Optional` **select**(`extensions`): [`ConnectedExtension`](ConnectedExtension.md)[] +▸ `Optional` **select**(`extensions`): [`AssignedExtension`](AssignedExtension.md)[] #### Parameters | Name | Type | | :------ | :------ | -| `extensions` | [`ConnectedExtension`](ConnectedExtension.md)[] | +| `extensions` | [`AssignedExtension`](AssignedExtension.md)[] | #### Returns -[`ConnectedExtension`](ConnectedExtension.md)[] +[`AssignedExtension`](AssignedExtension.md)[] #### Defined in diff --git a/packages/framework/esm-framework/docs/interfaces/OldExtensionSlotBaseProps.md b/packages/framework/esm-framework/docs/interfaces/OldExtensionSlotBaseProps.md index 5238524ae..3afe6c9f0 100644 --- a/packages/framework/esm-framework/docs/interfaces/OldExtensionSlotBaseProps.md +++ b/packages/framework/esm-framework/docs/interfaces/OldExtensionSlotBaseProps.md @@ -40,7 +40,7 @@ ___ ### state -• `Optional` **state**: `Record`<`string`, `any`\> +• `Optional` **state**: `Record`<`string`, `unknown`\> #### Defined in @@ -50,17 +50,17 @@ ___ ### select -▸ `Optional` **select**(`extensions`): [`ConnectedExtension`](ConnectedExtension.md)[] +▸ `Optional` **select**(`extensions`): [`AssignedExtension`](AssignedExtension.md)[] #### Parameters | Name | Type | | :------ | :------ | -| `extensions` | [`ConnectedExtension`](ConnectedExtension.md)[] | +| `extensions` | [`AssignedExtension`](AssignedExtension.md)[] | #### Returns -[`ConnectedExtension`](ConnectedExtension.md)[] +[`AssignedExtension`](AssignedExtension.md)[] #### Defined in diff --git a/packages/framework/esm-framework/docs/interfaces/WorkspaceContainerProps.md b/packages/framework/esm-framework/docs/interfaces/WorkspaceContainerProps.md index 07cd2b74e..9954cf2e2 100644 --- a/packages/framework/esm-framework/docs/interfaces/WorkspaceContainerProps.md +++ b/packages/framework/esm-framework/docs/interfaces/WorkspaceContainerProps.md @@ -19,7 +19,7 @@ #### Defined in -[packages/framework/esm-styleguide/src/workspaces/container/workspace-container.component.tsx:20](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/workspaces/container/workspace-container.component.tsx#L20) +[packages/framework/esm-styleguide/src/workspaces/container/workspace-container.component.tsx:19](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/workspaces/container/workspace-container.component.tsx#L19) ___ @@ -29,7 +29,7 @@ ___ #### Defined in -[packages/framework/esm-styleguide/src/workspaces/container/workspace-container.component.tsx:17](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/workspaces/container/workspace-container.component.tsx#L17) +[packages/framework/esm-styleguide/src/workspaces/container/workspace-container.component.tsx:16](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/workspaces/container/workspace-container.component.tsx#L16) ___ @@ -39,7 +39,7 @@ ___ #### Defined in -[packages/framework/esm-styleguide/src/workspaces/container/workspace-container.component.tsx:18](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/workspaces/container/workspace-container.component.tsx#L18) +[packages/framework/esm-styleguide/src/workspaces/container/workspace-container.component.tsx:17](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/workspaces/container/workspace-container.component.tsx#L17) ___ @@ -49,4 +49,4 @@ ___ #### Defined in -[packages/framework/esm-styleguide/src/workspaces/container/workspace-container.component.tsx:19](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/workspaces/container/workspace-container.component.tsx#L19) +[packages/framework/esm-styleguide/src/workspaces/container/workspace-container.component.tsx:18](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-styleguide/src/workspaces/container/workspace-container.component.tsx#L18) diff --git a/packages/framework/esm-framework/src/integration-tests/extension-config.test.tsx b/packages/framework/esm-framework/src/integration-tests/extension-config.test.tsx index 1a0b98101..8435d13cd 100644 --- a/packages/framework/esm-framework/src/integration-tests/extension-config.test.tsx +++ b/packages/framework/esm-framework/src/integration-tests/extension-config.test.tsx @@ -23,7 +23,7 @@ jest.mock('@openmrs/esm-api', () => { const original = jest.requireActual('@openmrs/esm-api'); return { ...original, - getSessionStore: () => mockSessionStore, + sessionStore: mockSessionStore, refetchCurrentUser: jest.fn(), }; }); diff --git a/packages/framework/esm-offline/jest.config.js b/packages/framework/esm-offline/jest.config.js index a22f29fb9..b1320037d 100644 --- a/packages/framework/esm-offline/jest.config.js +++ b/packages/framework/esm-offline/jest.config.js @@ -3,6 +3,7 @@ module.exports = { '^.+\\.tsx?$': ['@swc/jest'], }, moduleNameMapper: { + '^lodash-es$': 'lodash', '^lodash-es/(.*)$': 'lodash/$1', // See https://jestjs.io/docs/upgrading-to-jest28#packagejson-exports // which links to https://github.com/microsoft/accessibility-insights-web/pull/5421#issuecomment-1109168149 diff --git a/packages/framework/esm-react-utils/jest.config.js b/packages/framework/esm-react-utils/jest.config.js index 6e7fbccbe..a633605ef 100644 --- a/packages/framework/esm-react-utils/jest.config.js +++ b/packages/framework/esm-react-utils/jest.config.js @@ -4,6 +4,7 @@ module.exports = { }, setupFilesAfterEnv: ['/src/setup-tests.js'], moduleNameMapper: { + '^lodash-es$': 'lodash', '^lodash-es/(.*)$': 'lodash/$1', '@openmrs/esm-error-handling': '/__mocks__/openmrs-esm-error-handling.mock.ts', '@openmrs/esm-state': '@openmrs/esm-state/mock', diff --git a/packages/framework/esm-react-utils/src/Extension.tsx b/packages/framework/esm-react-utils/src/Extension.tsx index 4c97bad64..1cf29cf5d 100644 --- a/packages/framework/esm-react-utils/src/Extension.tsx +++ b/packages/framework/esm-react-utils/src/Extension.tsx @@ -1,17 +1,12 @@ /** @module @category Extension */ import { renderExtension } from '@openmrs/esm-extensions'; -import React, { useCallback, useContext, useEffect, useRef, useState, type ReactElement } from 'react'; -import type { Parcel } from 'single-spa'; +import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; +import { type Parcel } from 'single-spa'; import { ComponentContext } from '.'; -import type { ExtensionData } from './ComponentContext'; -export type ExtensionProps = { - state?: Record; - /** @deprecated Pass a function as the child of `ExtensionSlot` instead. */ - wrap?(slot: React.ReactNode, extension: ExtensionData): ReactElement | null; -} & Omit, 'children'> & { - children?: React.ReactNode | ((slot: React.ReactNode, extension?: ExtensionData) => React.ReactNode); - }; +export type ExtensionProps = React.HTMLAttributes & { + state?: Record; +}; /** * Represents the position in the DOM where each extension within @@ -22,24 +17,13 @@ export type ExtensionProps = { * Usage of this component *must* have an ancestor ``, * and *must* only be used once within that ``. */ -export const Extension: React.FC = ({ state, children, wrap, ...divProps }) => { +export const Extension: React.FC = ({ state, children, ...divProps }) => { const [domElement, setDomElement] = useState(); const { extension } = useContext(ComponentContext); const parcel = useRef(null); const updatePromise = useRef>(Promise.resolve()); const rendering = useRef(false); - useEffect(() => { - if (wrap) { - console.warn( - `'wrap' prop of Extension is being used ${ - extension?.extensionId ? `by ${extension.extensionId} in ${extension.extensionSlotName}` : '' - }. This will be removed in a future release.`, - ); - } - // we only warn when component mounts - }, []); - const ref = useCallback( (node: HTMLDivElement) => { setDomElement(node); @@ -123,13 +107,9 @@ export const Extension: React.FC = ({ state, children, wrap, ... // The extension is rendered into the `
`. The `
` has relative // positioning in order to allow the UI Editor to absolutely position // elements within it. - const slot = ( -
- ); - - if (typeof children === 'function' && !React.isValidElement(children)) { - return <>{children(slot, extension)}; - } - - return extension && wrap ? wrap(slot, extension) : slot; + return extension ? ( +
+ {children} +
+ ) : null; }; diff --git a/packages/framework/esm-react-utils/src/ExtensionSlot.tsx b/packages/framework/esm-react-utils/src/ExtensionSlot.tsx index 4c0b2c275..02a68944c 100644 --- a/packages/framework/esm-react-utils/src/ExtensionSlot.tsx +++ b/packages/framework/esm-react-utils/src/ExtensionSlot.tsx @@ -1,6 +1,6 @@ /** @module @category Extension */ import React, { useRef, useMemo } from 'react'; -import type { ConnectedExtension } from '@openmrs/esm-extensions'; +import { type AssignedExtension } from '@openmrs/esm-extensions'; import { ComponentContext } from './ComponentContext'; import { Extension } from './Extension'; import { useExtensionSlot } from './useExtensionSlot'; @@ -9,24 +9,24 @@ export interface ExtensionSlotBaseProps { name: string; /** @deprecated Use `name` */ extensionSlotName?: string; - select?: (extensions: Array) => Array; - state?: Record; + select?: (extensions: Array) => Array; + state?: Record; } export interface OldExtensionSlotBaseProps { name?: string; /** @deprecated Use `name` */ extensionSlotName: string; - select?: (extensions: Array) => Array; - state?: Record; + select?: (extensions: Array) => Array; + state?: Record; } export type ExtensionSlotProps = (OldExtensionSlotBaseProps | ExtensionSlotBaseProps) & Omit, 'children'> & { - children?: React.ReactNode | ((extension: ConnectedExtension) => React.ReactNode); + children?: React.ReactNode | ((extension: AssignedExtension, state?: Record) => React.ReactNode); }; -function defaultSelect(extensions: Array) { +function defaultSelect(extensions: Array) { return extensions; } @@ -101,7 +101,7 @@ export function ExtensionSlot({ const extensionsFromChildrenFunction = useMemo(() => { if (typeof children == 'function' && !React.isValidElement(children)) { - return extensionsToRender.map((extension) => children(extension)); + return extensionsToRender.map((extension) => children(extension, state)); } }, [children, extensionsToRender]); @@ -114,7 +114,7 @@ export function ExtensionSlot({ {...divProps} > {name && - extensionsToRender.map((extension, i) => ( + extensionsToRender?.map((extension, i) => ( - {extensionsFromChildrenFunction?.[i] ?? (typeof children != 'function' ? children : null) ?? ( + {extensionsFromChildrenFunction?.[i] ?? (typeof children !== 'function' ? children : null) ?? ( )} diff --git a/packages/framework/esm-react-utils/src/extensions.test.tsx b/packages/framework/esm-react-utils/src/extensions.test.tsx index aa7053ff0..10a80693f 100644 --- a/packages/framework/esm-react-utils/src/extensions.test.tsx +++ b/packages/framework/esm-react-utils/src/extensions.test.tsx @@ -122,21 +122,15 @@ describe('ExtensionSlot, Extension, and useExtensionSlotMeta', () => { disableTranslations: true, })(() => { const metas = useExtensionSlotMeta('Box'); - const wrapItem = useCallback( - (slot: React.ReactNode, extension?: ExtensionData) => { - return ( -
-

{metas[getExtensionNameFromId(extension?.extensionId ?? '')].code}

- {slot} -
- ); - }, - [metas], - ); return (
- {wrapItem} + {(extension) => ( +
+

{metas[getExtensionNameFromId(extension?.id ?? '')].code}

+ +
+ )}
); @@ -163,21 +157,15 @@ describe('ExtensionSlot, Extension, and useExtensionSlotMeta', () => { })(() => { const [suffix, toggleSuffix] = useReducer((suffix) => (suffix == '!' ? '?' : '!'), '!'); const metas = useExtensionSlotMeta('Box'); - const wrapItem = useCallback( - (slot: React.ReactNode, extension?: ExtensionData) => { - return ( -
-

{metas[getExtensionNameFromId(extension?.extensionId ?? '')].code}

- {slot} -
- ); - }, - [metas], - ); return (
- {wrapItem} + {(extension) => ( +
+

{metas[getExtensionNameFromId(extension?.id ?? '')].code}

+ +
+ )}
@@ -209,7 +197,7 @@ describe('ExtensionSlot, Extension, and useExtensionSlotMeta', () => { return (
- {(extension: ConnectedExtension) => ( + {(extension) => (

{extension.meta.code}

@@ -226,33 +214,6 @@ describe('ExtensionSlot, Extension, and useExtensionSlotMeta', () => { expect(within(screen.getByTestId('Hindi')).getByRole('heading')).toHaveTextContent('hi'); }); - test('Extension renders with child function', async () => { - registerSimpleExtension('Hindi', 'esm-languages-app', undefined, { - code: 'hi', - }); - attach('Box', 'Hindi'); - const App = openmrsComponentDecorator({ - moduleName: 'esm-languages-app', - featureName: 'Languages', - disableTranslations: true, - })(() => { - return ( -
- - {() => {(slot) =>
{slot}
}
} -
-
- ); - }); - - render(); - - await waitFor(() => expect(screen.getByTestId('custom-wrapper')).toBeInTheDocument()); - - // essentially: is the first child of custom-wrapper the extension? - expect(screen.getByTestId('custom-wrapper').children[0]).toHaveAttribute('data-extension-id', 'Hindi'); - }); - test('Extensions behind feature flags only render when their feature flag is enabled', async () => { registerSimpleExtension('Arabic', 'esm-languages-app'); registerSimpleExtension('Turkish', 'esm-languages-app', undefined, undefined, 'turkic'); diff --git a/packages/framework/esm-react-utils/src/index.ts b/packages/framework/esm-react-utils/src/index.ts index 3d5cdfc6a..6d0148090 100644 --- a/packages/framework/esm-react-utils/src/index.ts +++ b/packages/framework/esm-react-utils/src/index.ts @@ -20,6 +20,7 @@ export * from './useDefineAppContext'; export * from './useExtensionInternalStore'; export * from './useExtensionSlot'; export * from './useExtensionSlotMeta'; +export * from './useExtensionSlotStore'; export * from './useExtensionStore'; export * from './useFeatureFlag'; export * from './useForceUpdate'; diff --git a/packages/framework/esm-react-utils/src/public.ts b/packages/framework/esm-react-utils/src/public.ts index d9e2da022..f5e4b4ca1 100644 --- a/packages/framework/esm-react-utils/src/public.ts +++ b/packages/framework/esm-react-utils/src/public.ts @@ -18,6 +18,7 @@ export * from './useDebounce'; export * from './useDefineAppContext'; export * from './useExtensionSlotMeta'; export * from './useExtensionStore'; +export * from './useExtensionSlotStore'; export * from './useFeatureFlag'; export * from './useLayoutType'; export * from './useLocations'; diff --git a/packages/framework/esm-react-utils/src/useAssignedExtensionIds.ts b/packages/framework/esm-react-utils/src/useAssignedExtensionIds.ts index 8ebbd38b1..8b1647c27 100644 --- a/packages/framework/esm-react-utils/src/useAssignedExtensionIds.ts +++ b/packages/framework/esm-react-utils/src/useAssignedExtensionIds.ts @@ -1,7 +1,7 @@ /** @module @category Extension */ import { useEffect, useState } from 'react'; import { getExtensionStore } from '@openmrs/esm-extensions'; -import isEqual from 'lodash-es/isEqual'; +import { isEqual } from 'lodash-es'; /** * Gets the assigned extension ids for a given extension slot name. diff --git a/packages/framework/esm-react-utils/src/useAssignedExtensions.ts b/packages/framework/esm-react-utils/src/useAssignedExtensions.ts index 1918e859f..ebdbdf802 100644 --- a/packages/framework/esm-react-utils/src/useAssignedExtensions.ts +++ b/packages/framework/esm-react-utils/src/useAssignedExtensions.ts @@ -1,18 +1,11 @@ /** @module @category Extension */ -import { useMemo } from 'react'; -import { useExtensionStore } from './useExtensionStore'; +import { useExtensionSlotStore } from './useExtensionSlotStore'; /** * Gets the assigned extensions for a given extension slot name. - * Does not consider if offline or online. * @param slotName The name of the slot to get the assigned extensions for. */ export function useAssignedExtensions(slotName: string) { - const { slots } = useExtensionStore(); - - const extensions = useMemo(() => { - return slots[slotName]?.assignedExtensions ?? []; - }, [slots, slotName]); - - return extensions; + const slotStore = useExtensionSlotStore(slotName); + return slotStore?.assignedExtensions; } diff --git a/packages/framework/esm-react-utils/src/useConnectedExtensions.ts b/packages/framework/esm-react-utils/src/useConnectedExtensions.ts index 20b808df6..e6db27d84 100644 --- a/packages/framework/esm-react-utils/src/useConnectedExtensions.ts +++ b/packages/framework/esm-react-utils/src/useConnectedExtensions.ts @@ -1,31 +1,10 @@ /** @module @category Extension */ -import { useMemo } from 'react'; -import type { ConnectedExtension } from '@openmrs/esm-extensions'; -import { getConnectedExtensions } from '@openmrs/esm-extensions'; -import { useConnectivity } from './useConnectivity'; +import { type ConnectedExtension } from '@openmrs/esm-extensions'; import { useAssignedExtensions } from './useAssignedExtensions'; -import { useStore } from './useStore'; -import { featureFlagsStore } from '@openmrs/esm-feature-flags'; /** * Gets the assigned extension for a given extension slot name. - * Considers if offline or online, and what feature flags are enabled. * @param slotName The name of the slot to get the assigned extensions for. + * @deprecated Use useAssignedExtensions instead */ -export function useConnectedExtensions(slotName: string): Array { - const online = useConnectivity(); - const assignedExtensions = useAssignedExtensions(slotName); - const featureFlagStore = useStore(featureFlagsStore); - - const enabledFeatureFlags = useMemo(() => { - return Object.entries(featureFlagStore.flags) - .filter(([, { enabled }]) => enabled) - .map(([name]) => name); - }, [featureFlagStore.flags]); - - const connectedExtensions = useMemo(() => { - return getConnectedExtensions(assignedExtensions, online, enabledFeatureFlags); - }, [assignedExtensions, online, enabledFeatureFlags]); - - return connectedExtensions; -} +export const useConnectedExtensions = useAssignedExtensions as (slotName: string) => Array; diff --git a/packages/framework/esm-react-utils/src/useExtensionSlot.ts b/packages/framework/esm-react-utils/src/useExtensionSlot.ts index ab2e67104..fe0786d57 100644 --- a/packages/framework/esm-react-utils/src/useExtensionSlot.ts +++ b/packages/framework/esm-react-utils/src/useExtensionSlot.ts @@ -1,7 +1,7 @@ import { useContext, useEffect } from 'react'; import { registerExtensionSlot } from '@openmrs/esm-extensions'; import { ComponentContext } from './ComponentContext'; -import { useConnectedExtensions } from './useConnectedExtensions'; +import { useAssignedExtensions } from './useAssignedExtensions'; /** @internal */ export function useExtensionSlot(slotName: string) { @@ -15,7 +15,7 @@ export function useExtensionSlot(slotName: string) { registerExtensionSlot(moduleName, slotName); }, []); - const extensions = useConnectedExtensions(slotName); + const extensions = useAssignedExtensions(slotName); return { extensions, diff --git a/packages/framework/esm-react-utils/src/useExtensionSlotMeta.ts b/packages/framework/esm-react-utils/src/useExtensionSlotMeta.ts index 59fbbf76b..5f5fc525b 100644 --- a/packages/framework/esm-react-utils/src/useExtensionSlotMeta.ts +++ b/packages/framework/esm-react-utils/src/useExtensionSlotMeta.ts @@ -1,14 +1,14 @@ /** @module @category Extension */ import type { ExtensionMeta } from '@openmrs/esm-extensions'; import { useMemo } from 'react'; -import { useConnectedExtensions } from './useConnectedExtensions'; +import { useAssignedExtensions } from './useAssignedExtensions'; /** * Extract meta data from all extension for a given extension slot. * @param extensionSlotName */ export function useExtensionSlotMeta(extensionSlotName: string) { - const extensions = useConnectedExtensions(extensionSlotName); + const extensions = useAssignedExtensions(extensionSlotName); return useMemo(() => Object.fromEntries(extensions.map((ext) => [ext.name, ext.meta as T])), [extensions]); } diff --git a/packages/framework/esm-react-utils/src/useExtensionSlotStore.ts b/packages/framework/esm-react-utils/src/useExtensionSlotStore.ts new file mode 100644 index 000000000..cadc97462 --- /dev/null +++ b/packages/framework/esm-react-utils/src/useExtensionSlotStore.ts @@ -0,0 +1,6 @@ +/** @module @category Extension */ +import { type ExtensionSlotState, type ExtensionStore, getExtensionStore } from '@openmrs/esm-extensions'; +import { useStore } from './useStore'; + +export const useExtensionSlotStore = (slot: string) => + useStore(getExtensionStore(), (state) => state.slots?.[slot]); diff --git a/packages/framework/esm-react-utils/src/useExtensionStore.ts b/packages/framework/esm-react-utils/src/useExtensionStore.ts index c5f6781ba..8f7749168 100644 --- a/packages/framework/esm-react-utils/src/useExtensionStore.ts +++ b/packages/framework/esm-react-utils/src/useExtensionStore.ts @@ -1,6 +1,5 @@ /** @module @category Extension */ -import type { ExtensionStore } from '@openmrs/esm-extensions'; -import { getExtensionStore } from '@openmrs/esm-extensions'; +import { type ExtensionStore, getExtensionStore } from '@openmrs/esm-extensions'; import { createUseStore } from './useStore'; export const useExtensionStore = createUseStore(getExtensionStore()); diff --git a/packages/framework/esm-react-utils/src/useStore.ts b/packages/framework/esm-react-utils/src/useStore.ts index c4d7e6f92..48688c3cc 100644 --- a/packages/framework/esm-react-utils/src/useStore.ts +++ b/packages/framework/esm-react-utils/src/useStore.ts @@ -17,7 +17,7 @@ function bindActions(store: StoreApi, actions: Actions): BoundActions { const bound = {}; for (let i in actions) { - bound[i] = function (...args) { + bound[i] = function (...args: Array) { store.setState((state) => { let _args = [state, ...args]; return actions[i](..._args); @@ -28,13 +28,16 @@ function bindActions(store: StoreApi, actions: Actions): BoundActions { return bound; } -const defaultSelectFunction = (x) => x; +const defaultSelectFunction = + () => + (x: T) => + x as unknown as U; function useStore(store: StoreApi): T; function useStore(store: StoreApi, select: (state: T) => U): U; function useStore(store: StoreApi, select: undefined, actions: Actions): T & BoundActions; function useStore(store: StoreApi, select: (state: T) => U, actions: Actions): U & BoundActions; -function useStore(store: StoreApi, select: (state: T) => U = defaultSelectFunction, actions?: Actions) { +function useStore(store: StoreApi, select: (state: T) => U = defaultSelectFunction(), actions?: Actions) { const [state, setState] = useState(() => select(store.getState())); useEffect(() => subscribeTo(store, select, setState), [store, select]); @@ -50,7 +53,7 @@ function useStore(store: StoreApi, select: (state: T) => U = defaultSel * @returns */ function useStoreWithActions(store: StoreApi, actions: Actions): T & BoundActions { - return useStore(store, defaultSelectFunction, actions); + return useStore(store, defaultSelectFunction(), actions); } /** diff --git a/packages/framework/esm-routes/src/loaders/components.ts b/packages/framework/esm-routes/src/loaders/components.ts index 060d524f3..791fb7541 100644 --- a/packages/framework/esm-routes/src/loaders/components.ts +++ b/packages/framework/esm-routes/src/loaders/components.ts @@ -12,7 +12,7 @@ import { type WorkspaceDefinition, } from '@openmrs/esm-globals'; import { getLoader } from './app'; -import { FeatureFlag, registerFeatureFlag } from '@openmrs/esm-feature-flags'; +import { registerFeatureFlag } from '@openmrs/esm-feature-flags'; /** * This function registers an extension definition with the framework and will @@ -204,7 +204,7 @@ supported, so the workspace will not be loaded.`, * This function registers a workspace definition with the framework so that it can be launched. * * @param appName The name of the app defining this workspace - * @param workspace An object that describes the workspace, derived from `routes.json` + * @param featureFlag An object that describes the workspace, derived from `routes.json` */ export function tryRegisterFeatureFlag(appName: string, featureFlag: FeatureFlagDefinition) { const name = featureFlag.flagName; diff --git a/packages/framework/esm-state/package.json b/packages/framework/esm-state/package.json index 406e0fcff..4f78b7b8a 100644 --- a/packages/framework/esm-state/package.json +++ b/packages/framework/esm-state/package.json @@ -41,9 +41,11 @@ "zustand": "^4.5.5" }, "peerDependencies": { - "@openmrs/esm-globals": "5.x" + "@openmrs/esm-globals": "5.x", + "@openmrs/esm-utils": "5.x" }, "devDependencies": { - "@openmrs/esm-globals": "workspace:*" + "@openmrs/esm-globals": "workspace:*", + "@openmrs/esm-utils": "workspace:*" } } diff --git a/packages/framework/esm-state/src/state.ts b/packages/framework/esm-state/src/state.ts index ce0d4701f..94c8d527c 100644 --- a/packages/framework/esm-state/src/state.ts +++ b/packages/framework/esm-state/src/state.ts @@ -1,7 +1,8 @@ /** @module @category Store */ +import type {} from '@openmrs/esm-globals'; +import { shallowEqual } from '@openmrs/esm-utils'; import type { StoreApi } from 'zustand/vanilla'; import { createStore } from 'zustand/vanilla'; -import type {} from '@openmrs/esm-globals'; interface StoreEntity { value: StoreApi; @@ -31,7 +32,7 @@ export function createGlobalStore(name: string, initialState: T): StoreApi if (available) { if (available.active) { - console.error('Cannot override an existing store. Make sure that stores are only created once.'); + console.error(`Attempted to override the existing store ${name}. Make sure that stores are only created once.`); } else { available.value.setState(initialState, true); } @@ -63,7 +64,7 @@ export function registerGlobalStore(name: string, store: StoreApi): StoreA if (available) { if (available.active) { - console.error('Cannot override an existing store. Make sure that stores are only created once.'); + console.error(`Attempted to override the existing store ${name}. Make sure that stores are only created once.`); } else { available.value = store; } @@ -103,15 +104,25 @@ export function getGlobalStore(name: string, fallbackState?: T): StoreApi return available.value as StoreApi; } -export function subscribeTo(store: StoreApi, select: (state: T) => U, handle: (subState: U) => void) { - let previous = select(store.getState()); - - return store.subscribe((state) => { - const current = select(state); - - if (current !== previous) { - previous = current; - handle(current); +type SubscribeToArgs = [StoreApi, (state: T) => void] | [StoreApi, (state: T) => U, (state: U) => void]; + +export function subscribeTo(store: StoreApi, handle: (state: T) => void): () => void; +export function subscribeTo( + store: StoreApi, + select: (state: T) => U, + handle: (subState: U) => void, +): () => void; +export function subscribeTo(...args: SubscribeToArgs): () => void { + const [store, select, handle] = args; + const handler = typeof handle === 'undefined' ? (select as unknown as (state: U) => void) : handle; + const selector = typeof handle === 'undefined' ? (state: T) => state as unknown as U : (select as (state: T) => U); + + handler(selector(store.getState())); + return store.subscribe((state, previous) => { + const current = selector(state); + + if (!shallowEqual(previous, current)) { + handler(current); } }); } diff --git a/packages/framework/esm-styleguide/src/patient-banner/actions-menu/patient-banner-actions-menu.component.tsx b/packages/framework/esm-styleguide/src/patient-banner/actions-menu/patient-banner-actions-menu.component.tsx index 59784ad64..8b8be1e85 100644 --- a/packages/framework/esm-styleguide/src/patient-banner/actions-menu/patient-banner-actions-menu.component.tsx +++ b/packages/framework/esm-styleguide/src/patient-banner/actions-menu/patient-banner-actions-menu.component.tsx @@ -1,7 +1,7 @@ /** @module @category UI */ import React, { useMemo } from 'react'; import { OverflowMenuVertical } from '@carbon/react/icons'; -import { ExtensionSlot, useConnectedExtensions, usePatient } from '@openmrs/esm-react-utils'; +import { ExtensionSlot, useExtensionSlot } from '@openmrs/esm-react-utils'; import { getCoreTranslation } from '@openmrs/esm-translations'; import { CustomOverflowMenu } from '../../custom-overflow-menu/custom-overflow-menu.component'; import styles from './patient-banner-actions-menu.module.scss'; @@ -25,7 +25,7 @@ export function PatientBannerActionsMenu({ isDeceased, additionalActionsSlotState, }: PatientBannerActionsMenuProps) { - const patientActions = useConnectedExtensions(actionsSlotName); + const { extensions: patientActions } = useExtensionSlot(actionsSlotName); const patientActionsSlotState = useMemo( () => ({ patientUuid, patient, ...additionalActionsSlotState }), [patientUuid, additionalActionsSlotState], diff --git a/packages/framework/esm-styleguide/src/workspaces/container/workspace-container.component.tsx b/packages/framework/esm-styleguide/src/workspaces/container/workspace-container.component.tsx index ce62af9cf..879bfaeab 100644 --- a/packages/framework/esm-styleguide/src/workspaces/container/workspace-container.component.tsx +++ b/packages/framework/esm-styleguide/src/workspaces/container/workspace-container.component.tsx @@ -5,7 +5,6 @@ import { DownToBottom, Maximize, Minimize } from '@carbon/react/icons'; import { ComponentContext, ExtensionSlot, isDesktop, useBodyScrollLock, useLayoutType } from '@openmrs/esm-react-utils'; import { getCoreTranslation } from '@openmrs/esm-translations'; import { I18nextProvider, useTranslation } from 'react-i18next'; - import { ArrowLeftIcon, ArrowRightIcon, CloseIcon } from '../../icons'; import { WorkspaceNotification } from '../notification/workspace-notification.component'; import ActionMenu from './action-menu.component'; diff --git a/packages/framework/esm-styleguide/src/workspaces/container/workspace-container.test.tsx b/packages/framework/esm-styleguide/src/workspaces/container/workspace-container.test.tsx index 8e9708f12..8c7dfa8c7 100644 --- a/packages/framework/esm-styleguide/src/workspaces/container/workspace-container.test.tsx +++ b/packages/framework/esm-styleguide/src/workspaces/container/workspace-container.test.tsx @@ -1,6 +1,6 @@ /// import React from 'react'; -import { screen, render, within, renderHook, act } from '@testing-library/react'; +import { act, screen, renderHook, render, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { registerWorkspace } from '@openmrs/esm-extensions'; import { ComponentContext, isDesktop, useLayoutType } from '@openmrs/esm-react-utils'; @@ -17,6 +17,11 @@ jest.mock('./workspace-renderer.component.tsx', () => { }; }); +jest.mock('react-i18next', () => ({ + ...jest.requireActual('react-i18next'), + useTranslation: jest.fn().mockImplementation(() => ({ t: (arg: string) => arg })), +})); + const mockedIsDesktop = isDesktop as unknown as jest.Mock; const mockedUseLayoutType = useLayoutType as jest.Mock; @@ -195,8 +200,8 @@ describe('WorkspaceContainer in overlay mode', () => { it('opens with overridable title and closes', async () => { mockedUseLayoutType.mockReturnValue('small-desktop'); const user = userEvent.setup(); - act(() => launchWorkspace('patient-search', { workspaceTitle: 'Make an appointment' })); renderWorkspaceOverlay(); + act(() => launchWorkspace('patient-search', { workspaceTitle: 'Make an appointment' })); expect(screen.queryByRole('complementary')).toBeInTheDocument(); expectToBeVisible(screen.getByRole('complementary')); diff --git a/packages/framework/esm-styleguide/src/workspaces/workspaces.ts b/packages/framework/esm-styleguide/src/workspaces/workspaces.ts index 613556874..4b56873ef 100644 --- a/packages/framework/esm-styleguide/src/workspaces/workspaces.ts +++ b/packages/framework/esm-styleguide/src/workspaces/workspaces.ts @@ -208,7 +208,7 @@ export function launchWorkspace< function updateStoreWithNewWorkspace(workspaceToBeAdded: OpenWorkspace, restOfTheWorkspaces?: Array) { store.setState((state) => { const openWorkspaces = [workspaceToBeAdded, ...(restOfTheWorkspaces ?? state.openWorkspaces)]; - let workspaceWindowState = getUpdatedWorkspaceWindowState(openWorkspaces[0]); + let workspaceWindowState = getUpdatedWorkspaceWindowState(workspaceToBeAdded); return { ...state, @@ -233,8 +233,8 @@ export function launchWorkspace< } else if (isWorkspaceAlreadyOpen) { const openWorkspace = openWorkspaces[workspaceIndexInOpenWorkspaces]; // Only update the title if it hasn't been set by `setTitle` - if (openWorkspace.title == getWorkspaceTitle(openWorkspace, openWorkspace.additionalProps)) { - openWorkspace.title = getWorkspaceTitle(openWorkspace, newWorkspace.additionalProps); + if (openWorkspace.title === getWorkspaceTitle(openWorkspace, openWorkspace.additionalProps)) { + openWorkspace.title = getWorkspaceTitle(newWorkspace, newWorkspace.additionalProps); } openWorkspace.additionalProps = newWorkspace.additionalProps; const restOfTheWorkspaces = openWorkspaces.filter((w) => w.name != name); @@ -376,7 +376,7 @@ const initialState: WorkspaceStoreState = { export const workspaceStore = createGlobalStore('workspace', initialState); export function getWorkspaceStore() { - return getGlobalStore('workspace', initialState); + return workspaceStore; } export function updateWorkspaceWindowState(value: WorkspaceWindowState) { diff --git a/yarn.lock b/yarn.lock index 37ff6a025..3e0a1da42 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3369,9 +3369,11 @@ __metadata: resolution: "@openmrs/esm-state@workspace:packages/framework/esm-state" dependencies: "@openmrs/esm-globals": "workspace:*" + "@openmrs/esm-utils": "workspace:*" zustand: "npm:^4.5.5" peerDependencies: "@openmrs/esm-globals": 5.x + "@openmrs/esm-utils": 5.x languageName: unknown linkType: soft