@openmrs/esm-react-utils 5.3.3-pre.1237 → 5.3.3-pre.1247

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 (48) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/__mocks__/openmrs-esm-state.mock.ts +8 -20
  3. package/dist/openmrs-esm-react-utils.js.map +1 -1
  4. package/jest.config.js +9 -11
  5. package/package.json +7 -7
  6. package/src/ComponentContext.ts +2 -2
  7. package/src/ConfigurableLink.test.tsx +19 -23
  8. package/src/ConfigurableLink.tsx +5 -19
  9. package/src/Extension.tsx +36 -76
  10. package/src/ExtensionSlot.tsx +15 -24
  11. package/src/UserHasAccess.tsx +3 -7
  12. package/src/extensions.test.tsx +100 -179
  13. package/src/getLifecycle.ts +8 -18
  14. package/src/index.ts +31 -31
  15. package/src/openmrsComponentDecorator.test.tsx +12 -12
  16. package/src/openmrsComponentDecorator.tsx +12 -26
  17. package/src/public.ts +27 -27
  18. package/src/setup-tests.js +4 -4
  19. package/src/useAbortController.test.tsx +8 -8
  20. package/src/useAbortController.ts +1 -1
  21. package/src/useAssignedExtensionIds.ts +4 -5
  22. package/src/useAssignedExtensions.ts +3 -7
  23. package/src/useBodyScrollLock.ts +2 -2
  24. package/src/useConfig.test.tsx +96 -112
  25. package/src/useConfig.ts +15 -43
  26. package/src/useConnectedExtensions.ts +8 -17
  27. package/src/useConnectivity.ts +3 -6
  28. package/src/useDebounce.ts +1 -1
  29. package/src/useExtensionInternalStore.ts +3 -8
  30. package/src/useExtensionSlot.ts +5 -7
  31. package/src/useExtensionSlotMeta.ts +5 -11
  32. package/src/useExtensionStore.ts +3 -5
  33. package/src/useFeatureFlag.ts +4 -4
  34. package/src/useForceUpdate.ts +1 -1
  35. package/src/useLayoutType.ts +12 -13
  36. package/src/useLocations.tsx +3 -3
  37. package/src/useOnClickOutside.test.tsx +10 -10
  38. package/src/useOnClickOutside.ts +2 -5
  39. package/src/useOpenmrsSWR.ts +9 -9
  40. package/src/usePagination.ts +6 -18
  41. package/src/usePatient.ts +9 -13
  42. package/src/useSession.test.tsx +35 -43
  43. package/src/useSession.ts +9 -13
  44. package/src/useStore.test.ts +11 -13
  45. package/src/useStore.ts +10 -31
  46. package/src/useVisit.ts +14 -37
  47. package/src/useVisitTypes.ts +3 -3
  48. package/webpack.config.js +14 -14
package/jest.config.js CHANGED
@@ -1,19 +1,17 @@
1
1
  module.exports = {
2
2
  transform: {
3
- "^.+\\.(j|t)sx?$": ["@swc/jest"],
3
+ '^.+\\.(j|t)sx?$': ['@swc/jest'],
4
4
  },
5
- setupFilesAfterEnv: ["<rootDir>/src/setup-tests.js"],
5
+ setupFilesAfterEnv: ['<rootDir>/src/setup-tests.js'],
6
6
  moduleNameMapper: {
7
- "^lodash-es/(.*)$": "lodash/$1",
8
- "@openmrs/esm-error-handling":
9
- "<rootDir>/__mocks__/openmrs-esm-error-handling.mock.ts",
10
- "@openmrs/esm-state": "<rootDir>/__mocks__/openmrs-esm-state.mock.ts",
11
- "@openmrs/esm-styleguide":
12
- "<rootDir>/__mocks__/openmrs-esm-styleguide.mock.tsx",
13
- dexie: require.resolve("dexie"),
7
+ '^lodash-es/(.*)$': 'lodash/$1',
8
+ '@openmrs/esm-error-handling': '<rootDir>/__mocks__/openmrs-esm-error-handling.mock.ts',
9
+ '@openmrs/esm-state': '<rootDir>/__mocks__/openmrs-esm-state.mock.ts',
10
+ '@openmrs/esm-styleguide': '<rootDir>/__mocks__/openmrs-esm-styleguide.mock.tsx',
11
+ dexie: require.resolve('dexie'),
14
12
  },
15
- testEnvironment: "jsdom",
13
+ testEnvironment: 'jsdom',
16
14
  testEnvironmentOptions: {
17
- url: "http://localhost/",
15
+ url: 'http://localhost/',
18
16
  },
19
17
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openmrs/esm-react-utils",
3
- "version": "5.3.3-pre.1237",
3
+ "version": "5.3.3-pre.1247",
4
4
  "license": "MPL-2.0",
5
5
  "description": "React utilities for OpenMRS.",
6
6
  "browser": "dist/openmrs-esm-react-utils.js",
@@ -57,12 +57,12 @@
57
57
  "swr": "2.x"
58
58
  },
59
59
  "devDependencies": {
60
- "@openmrs/esm-api": "5.3.3-pre.1237",
61
- "@openmrs/esm-config": "5.3.3-pre.1237",
62
- "@openmrs/esm-error-handling": "5.3.3-pre.1237",
63
- "@openmrs/esm-extensions": "5.3.3-pre.1237",
64
- "@openmrs/esm-feature-flags": "5.3.3-pre.1237",
65
- "@openmrs/esm-globals": "5.3.3-pre.1237",
60
+ "@openmrs/esm-api": "5.3.3-pre.1247",
61
+ "@openmrs/esm-config": "5.3.3-pre.1247",
62
+ "@openmrs/esm-error-handling": "5.3.3-pre.1247",
63
+ "@openmrs/esm-extensions": "5.3.3-pre.1247",
64
+ "@openmrs/esm-feature-flags": "5.3.3-pre.1247",
65
+ "@openmrs/esm-globals": "5.3.3-pre.1247",
66
66
  "dayjs": "^1.10.8",
67
67
  "i18next": "^21.10.0",
68
68
  "react": "^18.1.0",
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import React from 'react';
2
2
 
3
3
  export interface ExtensionData {
4
4
  extensionSlotName: string;
@@ -15,5 +15,5 @@ export interface ComponentConfig {
15
15
  * Available to all components. Provided by `openmrsComponentDecorator`.
16
16
  */
17
17
  export const ComponentContext = React.createContext<ComponentConfig>({
18
- moduleName: "",
18
+ moduleName: '',
19
19
  });
@@ -1,46 +1,42 @@
1
- import React from "react";
2
- import "@testing-library/jest-dom/extend-expect";
3
- import { render, screen } from "@testing-library/react";
4
- import userEvent from "@testing-library/user-event";
5
- import { navigate, interpolateUrl } from "@openmrs/esm-config";
6
- import { ConfigurableLink } from "./ConfigurableLink";
1
+ import React from 'react';
2
+ import '@testing-library/jest-dom/extend-expect';
3
+ import { render, screen } from '@testing-library/react';
4
+ import userEvent from '@testing-library/user-event';
5
+ import { navigate, interpolateUrl } from '@openmrs/esm-config';
6
+ import { ConfigurableLink } from './ConfigurableLink';
7
7
 
8
- jest.mock("single-spa");
8
+ jest.mock('single-spa');
9
9
 
10
- jest.mock("@openmrs/esm-config");
10
+ jest.mock('@openmrs/esm-config');
11
11
  const mockNavigate = navigate as jest.Mock;
12
12
 
13
- const realInterpolate = jest.requireActual(
14
- "@openmrs/esm-config"
15
- ).interpolateUrl;
13
+ const realInterpolate = jest.requireActual('@openmrs/esm-config').interpolateUrl;
16
14
 
17
- (interpolateUrl as jest.Mock).mockImplementation((...args) =>
18
- realInterpolate(...args)
19
- );
15
+ (interpolateUrl as jest.Mock).mockImplementation((...args) => realInterpolate(...args));
20
16
 
21
17
  describe(`ConfigurableLink`, () => {
22
- const path = "${openmrsSpaBase}/home";
18
+ const path = '${openmrsSpaBase}/home';
23
19
  beforeEach(() => {
24
20
  mockNavigate.mockClear();
25
21
  render(
26
22
  <ConfigurableLink to={path} className="fancy-link">
27
23
  SPA Home
28
- </ConfigurableLink>
24
+ </ConfigurableLink>,
29
25
  );
30
26
  });
31
27
 
32
28
  it(`interpolates the link`, async () => {
33
- const link = screen.getByRole("link", { name: /spa home/i });
29
+ const link = screen.getByRole('link', { name: /spa home/i });
34
30
  expect(link).toBeTruthy();
35
- expect(link.closest("a")).toHaveClass("fancy-link");
36
- expect(link.closest("a")).toHaveAttribute("href", "/openmrs/spa/home");
31
+ expect(link.closest('a')).toHaveClass('fancy-link');
32
+ expect(link.closest('a')).toHaveAttribute('href', '/openmrs/spa/home');
37
33
  });
38
34
 
39
35
  it(`calls navigate on normal click but not special clicks`, async () => {
40
36
  const user = userEvent.setup();
41
37
 
42
- const link = screen.getByRole("link", { name: /spa home/i });
43
- await user.pointer({ target: link, keys: "[MouseRight]" });
38
+ const link = screen.getByRole('link', { name: /spa home/i });
39
+ await user.pointer({ target: link, keys: '[MouseRight]' });
44
40
  expect(navigate).not.toHaveBeenCalled();
45
41
  await user.click(link);
46
42
  expect(navigate).toHaveBeenCalledWith({ to: path });
@@ -50,8 +46,8 @@ describe(`ConfigurableLink`, () => {
50
46
  const user = userEvent.setup();
51
47
 
52
48
  expect(navigate).not.toHaveBeenCalled();
53
- const link = screen.getByRole("link", { name: /spa home/i });
54
- await user.type(link, "{enter}");
49
+ const link = screen.getByRole('link', { name: /spa home/i });
50
+ await user.type(link, '{enter}');
55
51
  expect(navigate).toHaveBeenCalledWith({ to: path });
56
52
  });
57
53
  });
@@ -1,22 +1,9 @@
1
1
  /** @module @category Navigation */
2
- import React, {
3
- type MouseEvent,
4
- type AnchorHTMLAttributes,
5
- type PropsWithChildren,
6
- } from "react";
7
- import { navigate, interpolateUrl, TemplateParams } from "@openmrs/esm-config";
2
+ import React, { type MouseEvent, type AnchorHTMLAttributes, type PropsWithChildren } from 'react';
3
+ import { navigate, interpolateUrl, TemplateParams } from '@openmrs/esm-config';
8
4
 
9
- function handleClick(
10
- event: MouseEvent,
11
- to: string,
12
- templateParams?: TemplateParams
13
- ) {
14
- if (
15
- !event.metaKey &&
16
- !event.ctrlKey &&
17
- !event.shiftKey &&
18
- event.button == 0
19
- ) {
5
+ function handleClick(event: MouseEvent, to: string, templateParams?: TemplateParams) {
6
+ if (!event.metaKey && !event.ctrlKey && !event.shiftKey && event.button == 0) {
20
7
  event.preventDefault();
21
8
  navigate({ to, templateParams });
22
9
  }
@@ -25,8 +12,7 @@ function handleClick(
25
12
  /**
26
13
  * @noInheritDoc
27
14
  */
28
- export interface ConfigurableLinkProps
29
- extends AnchorHTMLAttributes<HTMLAnchorElement> {
15
+ export interface ConfigurableLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
30
16
  to: string;
31
17
  templateParams?: TemplateParams;
32
18
  }
package/src/Extension.tsx CHANGED
@@ -1,27 +1,15 @@
1
- import { renderExtension } from "@openmrs/esm-extensions";
2
- import React, {
3
- useCallback,
4
- useContext,
5
- useEffect,
6
- useRef,
7
- useState,
8
- type ReactElement,
9
- } from "react";
10
- import type { Parcel } from "single-spa";
11
- import { ComponentContext } from ".";
12
- import type { ExtensionData } from "./ComponentContext";
1
+ import { renderExtension } from '@openmrs/esm-extensions';
2
+ import React, { useCallback, useContext, useEffect, useRef, useState, type ReactElement } from 'react';
3
+ import type { Parcel } from 'single-spa';
4
+ import { ComponentContext } from '.';
5
+ import type { ExtensionData } from './ComponentContext';
13
6
 
14
7
  export type ExtensionProps = {
15
8
  state?: Record<string, any>;
16
9
  /** @deprecated Pass a function as the child of `ExtensionSlot` instead. */
17
- wrap?(
18
- slot: React.ReactNode,
19
- extension: ExtensionData
20
- ): ReactElement<any, any> | null;
21
- } & Omit<React.HTMLAttributes<HTMLDivElement>, "children"> & {
22
- children?:
23
- | React.ReactNode
24
- | ((slot: React.ReactNode, extension?: ExtensionData) => React.ReactNode);
10
+ wrap?(slot: React.ReactNode, extension: ExtensionData): ReactElement<any, any> | null;
11
+ } & Omit<React.HTMLAttributes<HTMLDivElement>, 'children'> & {
12
+ children?: React.ReactNode | ((slot: React.ReactNode, extension?: ExtensionData) => React.ReactNode);
25
13
  };
26
14
 
27
15
  /**
@@ -33,12 +21,7 @@ export type ExtensionProps = {
33
21
  * Usage of this component *must* have an ancestor `<ExtensionSlot>`,
34
22
  * and *must* only be used once within that `<ExtensionSlot>`.
35
23
  */
36
- export const Extension: React.FC<ExtensionProps> = ({
37
- state,
38
- children,
39
- wrap,
40
- ...divProps
41
- }) => {
24
+ export const Extension: React.FC<ExtensionProps> = ({ state, children, wrap, ...divProps }) => {
42
25
  const [domElement, setDomElement] = useState<HTMLDivElement>();
43
26
  const { extension } = useContext(ComponentContext);
44
27
  const parcel = useRef<Parcel | null>(null);
@@ -49,10 +32,8 @@ export const Extension: React.FC<ExtensionProps> = ({
49
32
  if (wrap) {
50
33
  console.warn(
51
34
  `'wrap' prop of Extension is being used ${
52
- extension?.extensionId
53
- ? `by ${extension.extensionId} in ${extension.extensionSlotName}`
54
- : ""
55
- }. This will be removed in a future release.`
35
+ extension?.extensionId ? `by ${extension.extensionId} in ${extension.extensionSlotName}` : ''
36
+ }. This will be removed in a future release.`,
56
37
  );
57
38
  }
58
39
  // we only warn when component mounts
@@ -63,7 +44,7 @@ export const Extension: React.FC<ExtensionProps> = ({
63
44
  (node: HTMLDivElement) => {
64
45
  setDomElement(node);
65
46
  },
66
- [setDomElement]
47
+ [setDomElement],
67
48
  );
68
49
 
69
50
  useEffect(() => {
@@ -82,7 +63,7 @@ export const Extension: React.FC<ExtensionProps> = ({
82
63
  extension.extensionSlotModuleName,
83
64
  extension.extensionId,
84
65
  undefined,
85
- state
66
+ state,
86
67
  ).then((newParcel) => {
87
68
  parcel.current = newParcel;
88
69
  rendering.current = false;
@@ -92,20 +73,20 @@ export const Extension: React.FC<ExtensionProps> = ({
92
73
  if (parcel && parcel.current) {
93
74
  const status = parcel.current.getStatus();
94
75
  switch (status) {
95
- case "MOUNTING":
76
+ case 'MOUNTING':
96
77
  parcel.current.mountPromise.then(() => {
97
- if (parcel.current?.getStatus() === "MOUNTED") {
78
+ if (parcel.current?.getStatus() === 'MOUNTED') {
98
79
  parcel.current.unmount();
99
80
  }
100
81
  });
101
82
  break;
102
- case "MOUNTED":
83
+ case 'MOUNTED':
103
84
  parcel.current.unmount();
104
85
  break;
105
- case "UPDATING":
86
+ case 'UPDATING':
106
87
  if (updatePromise.current) {
107
88
  updatePromise.current.then(() => {
108
- if (parcel.current?.getStatus() === "MOUNTED") {
89
+ if (parcel.current?.getStatus() === 'MOUNTED') {
109
90
  parcel.current.unmount();
110
91
  }
111
92
  });
@@ -118,41 +99,25 @@ export const Extension: React.FC<ExtensionProps> = ({
118
99
  // we intentionally do not re-run this hook if state gets updated
119
100
  // state updates are handled in the next useEffect hook
120
101
  // eslint-disable-next-line react-hooks/exhaustive-deps
121
- }, [
122
- extension?.extensionSlotName,
123
- extension?.extensionId,
124
- extension?.extensionSlotModuleName,
125
- domElement,
126
- ]);
102
+ }, [extension?.extensionSlotName, extension?.extensionId, extension?.extensionSlotModuleName, domElement]);
127
103
 
128
104
  useEffect(() => {
129
- if (
130
- parcel.current &&
131
- parcel.current.update &&
132
- parcel.current.getStatus() !== "UNMOUNTING"
133
- ) {
134
- Promise.all([parcel.current.mountPromise, updatePromise.current]).then(
135
- () => {
136
- if (
137
- parcel?.current?.getStatus() === "MOUNTED" &&
138
- parcel.current.update
139
- ) {
140
- updatePromise.current = parcel.current
141
- .update({ ...state })
142
- .catch((err) => {
143
- // if we were trying to update but the component was unmounted
144
- // while this was happening, ignore the error
145
- if (
146
- !(err instanceof Error) ||
147
- !err.message.includes("minified message #32") ||
148
- parcel.current?.getStatus() === "MOUNTED"
149
- ) {
150
- throw err;
151
- }
152
- });
153
- }
105
+ if (parcel.current && parcel.current.update && parcel.current.getStatus() !== 'UNMOUNTING') {
106
+ Promise.all([parcel.current.mountPromise, updatePromise.current]).then(() => {
107
+ if (parcel?.current?.getStatus() === 'MOUNTED' && parcel.current.update) {
108
+ updatePromise.current = parcel.current.update({ ...state }).catch((err) => {
109
+ // if we were trying to update but the component was unmounted
110
+ // while this was happening, ignore the error
111
+ if (
112
+ !(err instanceof Error) ||
113
+ !err.message.includes('minified message #32') ||
114
+ parcel.current?.getStatus() === 'MOUNTED'
115
+ ) {
116
+ throw err;
117
+ }
118
+ });
154
119
  }
155
- );
120
+ });
156
121
  }
157
122
  }, [state]);
158
123
 
@@ -160,15 +125,10 @@ export const Extension: React.FC<ExtensionProps> = ({
160
125
  // positioning in order to allow the UI Editor to absolutely position
161
126
  // elements within it.
162
127
  const slot = (
163
- <div
164
- ref={ref}
165
- data-extension-id={extension?.extensionId}
166
- style={{ position: "relative" }}
167
- {...divProps}
168
- />
128
+ <div ref={ref} data-extension-id={extension?.extensionId} style={{ position: 'relative' }} {...divProps} />
169
129
  );
170
130
 
171
- if (typeof children === "function" && !React.isValidElement(children)) {
131
+ if (typeof children === 'function' && !React.isValidElement(children)) {
172
132
  return <>{children(slot, extension)}</>;
173
133
  }
174
134
 
@@ -1,8 +1,8 @@
1
- import React, { useRef, useMemo } from "react";
2
- import type { ConnectedExtension } from "@openmrs/esm-extensions";
3
- import { ComponentContext } from "./ComponentContext";
4
- import { Extension } from "./Extension";
5
- import { useExtensionSlot } from "./useExtensionSlot";
1
+ import React, { useRef, useMemo } from 'react';
2
+ import type { ConnectedExtension } from '@openmrs/esm-extensions';
3
+ import { ComponentContext } from './ComponentContext';
4
+ import { Extension } from './Extension';
5
+ import { useExtensionSlot } from './useExtensionSlot';
6
6
 
7
7
  export interface ExtensionSlotBaseProps {
8
8
  name: string;
@@ -20,14 +20,9 @@ export interface OldExtensionSlotBaseProps {
20
20
  state?: Record<string, any>;
21
21
  }
22
22
 
23
- export type ExtensionSlotProps = (
24
- | OldExtensionSlotBaseProps
25
- | ExtensionSlotBaseProps
26
- ) &
27
- Omit<React.HTMLAttributes<HTMLDivElement>, "children"> & {
28
- children?:
29
- | React.ReactNode
30
- | ((extension: ConnectedExtension) => React.ReactNode);
23
+ export type ExtensionSlotProps = (OldExtensionSlotBaseProps | ExtensionSlotBaseProps) &
24
+ Omit<React.HTMLAttributes<HTMLDivElement>, 'children'> & {
25
+ children?: React.ReactNode | ((extension: ConnectedExtension) => React.ReactNode);
31
26
  };
32
27
 
33
28
  function defaultSelect(extensions: Array<ConnectedExtension>) {
@@ -93,7 +88,7 @@ export function ExtensionSlot({
93
88
  }: ExtensionSlotProps) {
94
89
  if (children && state) {
95
90
  throw new Error(
96
- "Both children and state have been provided. If children are provided, the state must be passed as a prop to the `Extension` component."
91
+ 'Both children and state have been provided. If children are provided, the state must be passed as a prop to the `Extension` component.',
97
92
  );
98
93
  }
99
94
 
@@ -101,13 +96,10 @@ export function ExtensionSlot({
101
96
  const slotRef = useRef(null);
102
97
  const { extensions, extensionSlotModuleName } = useExtensionSlot(name);
103
98
 
104
- const extensionsToRender = useMemo(
105
- () => select(extensions),
106
- [select, extensions]
107
- );
99
+ const extensionsToRender = useMemo(() => select(extensions), [select, extensions]);
108
100
 
109
101
  const extensionsFromChildrenFunction = useMemo(() => {
110
- if (typeof children == "function" && !React.isValidElement(children)) {
102
+ if (typeof children == 'function' && !React.isValidElement(children)) {
111
103
  return extensionsToRender.map((extension) => children(extension));
112
104
  }
113
105
  }, [children, extensionsToRender]);
@@ -117,7 +109,7 @@ export function ExtensionSlot({
117
109
  ref={slotRef}
118
110
  data-extension-slot-name={name}
119
111
  data-extension-slot-module-name={extensionSlotModuleName}
120
- style={{ ...style, position: "relative" }}
112
+ style={{ ...style, position: 'relative' }}
121
113
  {...divProps}
122
114
  >
123
115
  {name &&
@@ -133,10 +125,9 @@ export function ExtensionSlot({
133
125
  },
134
126
  }}
135
127
  >
136
- {extensionsFromChildrenFunction?.[i] ??
137
- (typeof children != "function" ? children : null) ?? (
138
- <Extension state={state} />
139
- )}
128
+ {extensionsFromChildrenFunction?.[i] ?? (typeof children != 'function' ? children : null) ?? (
129
+ <Extension state={state} />
130
+ )}
140
131
  </ComponentContext.Provider>
141
132
  ))}
142
133
  </div>
@@ -1,6 +1,6 @@
1
1
  /** @module @category API */
2
- import { getCurrentUser, LoggedInUser, userHasAccess } from "@openmrs/esm-api";
3
- import React, { useEffect, useState } from "react";
2
+ import { getCurrentUser, LoggedInUser, userHasAccess } from '@openmrs/esm-api';
3
+ import React, { useEffect, useState } from 'react';
4
4
 
5
5
  export interface UserHasAccessProps {
6
6
  privilege: string | string[];
@@ -8,11 +8,7 @@ export interface UserHasAccessProps {
8
8
  children?: React.ReactNode;
9
9
  }
10
10
 
11
- export const UserHasAccess: React.FC<UserHasAccessProps> = ({
12
- privilege,
13
- fallback,
14
- children,
15
- }) => {
11
+ export const UserHasAccess: React.FC<UserHasAccessProps> = ({ privilege, fallback, children }) => {
16
12
  const [user, setUser] = useState<LoggedInUser | null>(null);
17
13
 
18
14
  useEffect(() => {