@medplum/react 2.0.18 → 2.0.19

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 (187) hide show
  1. package/dist/cjs/index.cjs +175 -77
  2. package/dist/cjs/index.cjs.map +1 -1
  3. package/dist/cjs/index.min.cjs +1 -1
  4. package/dist/esm/AppShell/Navbar.mjs +83 -32
  5. package/dist/esm/AppShell/Navbar.mjs.map +1 -1
  6. package/dist/esm/SearchFilterValueDisplay/SearchFilterValueDisplay.mjs +3 -2
  7. package/dist/esm/SearchFilterValueDisplay/SearchFilterValueDisplay.mjs.map +1 -1
  8. package/dist/esm/ValueSetAutocomplete/ValueSetAutocomplete.mjs +3 -0
  9. package/dist/esm/ValueSetAutocomplete/ValueSetAutocomplete.mjs.map +1 -1
  10. package/dist/esm/index.min.mjs +1 -1
  11. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/createReactComponent.mjs +2 -1
  12. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/createReactComponent.mjs.map +1 -1
  13. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/defaultAttributes.mjs +1 -1
  14. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/defaultAttributes.mjs.map +1 -1
  15. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconAdjustmentsHorizontal.mjs +2 -1
  16. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconAdjustmentsHorizontal.mjs.map +1 -1
  17. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconAlertCircle.mjs +2 -1
  18. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconAlertCircle.mjs.map +1 -1
  19. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBleach.mjs +2 -1
  20. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBleach.mjs.map +1 -1
  21. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBleachOff.mjs +2 -1
  22. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBleachOff.mjs.map +1 -1
  23. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBoxMultiple.mjs +2 -1
  24. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBoxMultiple.mjs.map +1 -1
  25. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBracketsContain.mjs +2 -1
  26. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBracketsContain.mjs.map +1 -1
  27. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBucket.mjs +2 -1
  28. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBucket.mjs.map +1 -1
  29. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBucketOff.mjs +2 -1
  30. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconBucketOff.mjs.map +1 -1
  31. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCalendar.mjs +2 -1
  32. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCalendar.mjs.map +1 -1
  33. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCheck.mjs +2 -1
  34. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCheck.mjs.map +1 -1
  35. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCheckbox.mjs +2 -1
  36. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCheckbox.mjs.map +1 -1
  37. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconChevronDown.mjs +2 -1
  38. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconChevronDown.mjs.map +1 -1
  39. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCircleMinus.mjs +2 -1
  40. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCircleMinus.mjs.map +1 -1
  41. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCirclePlus.mjs +2 -1
  42. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCirclePlus.mjs.map +1 -1
  43. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCloudUpload.mjs +2 -1
  44. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCloudUpload.mjs.map +1 -1
  45. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconColumns.mjs +2 -1
  46. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconColumns.mjs.map +1 -1
  47. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCurrencyDollar.mjs +2 -1
  48. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconCurrencyDollar.mjs.map +1 -1
  49. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconDots.mjs +2 -1
  50. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconDots.mjs.map +1 -1
  51. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconEdit.mjs +2 -1
  52. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconEdit.mjs.map +1 -1
  53. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconEqual.mjs +2 -1
  54. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconEqual.mjs.map +1 -1
  55. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconEqualNot.mjs +2 -1
  56. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconEqualNot.mjs.map +1 -1
  57. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconFileAlert.mjs +2 -1
  58. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconFileAlert.mjs.map +1 -1
  59. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconFilePlus.mjs +2 -1
  60. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconFilePlus.mjs.map +1 -1
  61. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconFilter.mjs +2 -1
  62. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconFilter.mjs.map +1 -1
  63. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconListDetails.mjs +2 -1
  64. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconListDetails.mjs.map +1 -1
  65. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconLogout.mjs +4 -2
  66. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconLogout.mjs.map +1 -1
  67. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconMathGreater.mjs +2 -1
  68. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconMathGreater.mjs.map +1 -1
  69. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconMathLower.mjs +2 -1
  70. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconMathLower.mjs.map +1 -1
  71. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconMessage.mjs +2 -1
  72. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconMessage.mjs.map +1 -1
  73. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconPin.mjs +2 -1
  74. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconPin.mjs.map +1 -1
  75. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconPinnedOff.mjs +2 -1
  76. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconPinnedOff.mjs.map +1 -1
  77. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconPlus.mjs +2 -1
  78. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconPlus.mjs.map +1 -1
  79. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSearch.mjs +2 -1
  80. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSearch.mjs.map +1 -1
  81. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSettings.mjs +2 -1
  82. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSettings.mjs.map +1 -1
  83. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSortAscending.mjs +2 -1
  84. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSortAscending.mjs.map +1 -1
  85. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSortDescending.mjs +2 -1
  86. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSortDescending.mjs.map +1 -1
  87. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSquare.mjs +2 -1
  88. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSquare.mjs.map +1 -1
  89. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSwitchHorizontal.mjs +2 -1
  90. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconSwitchHorizontal.mjs.map +1 -1
  91. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconTableExport.mjs +2 -1
  92. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconTableExport.mjs.map +1 -1
  93. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconTrash.mjs +2 -1
  94. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconTrash.mjs.map +1 -1
  95. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconX.mjs +2 -1
  96. package/dist/esm/node_modules/@tabler/icons-react/dist/esm/icons/IconX.mjs.map +1 -1
  97. package/dist/types/AddressDisplay/AddressDisplay.d.ts +1 -0
  98. package/dist/types/AddressInput/AddressInput.d.ts +1 -0
  99. package/dist/types/AnnotationInput/AnnotationInput.d.ts +1 -0
  100. package/dist/types/AppShell/HeaderSearchInput.d.ts +1 -0
  101. package/dist/types/AppShell/Navbar.d.ts +1 -0
  102. package/dist/types/AsyncAutocomplete/AsyncAutocomplete.d.ts +1 -0
  103. package/dist/types/AttachmentArrayDisplay/AttachmentArrayDisplay.d.ts +1 -0
  104. package/dist/types/AttachmentArrayInput/AttachmentArrayInput.d.ts +1 -0
  105. package/dist/types/AttachmentDisplay/AttachmentDisplay.d.ts +1 -0
  106. package/dist/types/AttachmentInput/AttachmentInput.d.ts +1 -0
  107. package/dist/types/BackboneElementDisplay/BackboneElementDisplay.d.ts +1 -0
  108. package/dist/types/BackboneElementInput/BackboneElementInput.d.ts +1 -0
  109. package/dist/types/BookmarkDialog/BookmarkDialog.d.ts +1 -0
  110. package/dist/types/CalendarInput/CalendarInput.d.ts +1 -0
  111. package/dist/types/CodeInput/CodeInput.d.ts +1 -0
  112. package/dist/types/CodeableConceptDisplay/CodeableConceptDisplay.d.ts +1 -0
  113. package/dist/types/CodeableConceptInput/CodeableConceptInput.d.ts +1 -0
  114. package/dist/types/CodingDisplay/CodingDisplay.d.ts +1 -0
  115. package/dist/types/CodingInput/CodingInput.d.ts +1 -0
  116. package/dist/types/ContactDetailDisplay/ContactDetailDisplay.d.ts +1 -0
  117. package/dist/types/ContactDetailInput/ContactDetailInput.d.ts +1 -0
  118. package/dist/types/ContactPointDisplay/ContactPointDisplay.d.ts +1 -0
  119. package/dist/types/ContactPointInput/ContactPointInput.d.ts +1 -0
  120. package/dist/types/Container/Container.d.ts +1 -0
  121. package/dist/types/DateTimeInput/DateTimeInput.d.ts +1 -0
  122. package/dist/types/DefaultResourceTimeline/DefaultResourceTimeline.d.ts +1 -0
  123. package/dist/types/DiagnosticReportDisplay/DiagnosticReportDisplay.d.ts +1 -0
  124. package/dist/types/Document/Document.d.ts +1 -0
  125. package/dist/types/EncounterTimeline/EncounterTimeline.d.ts +1 -0
  126. package/dist/types/ExtensionInput/ExtensionInput.d.ts +1 -0
  127. package/dist/types/FhirPathDisplay/FhirPathDisplay.d.ts +1 -0
  128. package/dist/types/GoogleButton/GoogleButton.d.ts +1 -0
  129. package/dist/types/HumanNameDisplay/HumanNameDisplay.d.ts +1 -0
  130. package/dist/types/HumanNameInput/HumanNameInput.d.ts +1 -0
  131. package/dist/types/IdentifierDisplay/IdentifierDisplay.d.ts +1 -0
  132. package/dist/types/IdentifierInput/IdentifierInput.d.ts +1 -0
  133. package/dist/types/Loading/Loading.d.ts +1 -0
  134. package/dist/types/Logo/Logo.d.ts +1 -0
  135. package/dist/types/MoneyDisplay/MoneyDisplay.d.ts +1 -0
  136. package/dist/types/MoneyInput/MoneyInput.d.ts +1 -0
  137. package/dist/types/NoteDisplay/NoteDisplay.d.ts +1 -0
  138. package/dist/types/OperationOutcomeAlert/OperationOutcomeAlert.d.ts +1 -0
  139. package/dist/types/Panel/Panel.d.ts +1 -0
  140. package/dist/types/PatientTimeline/PatientTimeline.d.ts +1 -0
  141. package/dist/types/PeriodInput/PeriodInput.d.ts +1 -0
  142. package/dist/types/PlanDefinitionBuilder/PlanDefinitionBuilder.d.ts +1 -0
  143. package/dist/types/QuantityDisplay/QuantityDisplay.d.ts +1 -0
  144. package/dist/types/QuantityInput/QuantityInput.d.ts +1 -0
  145. package/dist/types/QuestionnaireBuilder/QuestionnaireBuilder.d.ts +1 -0
  146. package/dist/types/QuestionnaireForm/QuestionnaireForm.d.ts +1 -0
  147. package/dist/types/RangeDisplay/RangeDisplay.d.ts +1 -0
  148. package/dist/types/RangeInput/RangeInput.d.ts +1 -0
  149. package/dist/types/RatioDisplay/RatioDisplay.d.ts +1 -0
  150. package/dist/types/RatioInput/RatioInput.d.ts +1 -0
  151. package/dist/types/ReferenceDisplay/ReferenceDisplay.d.ts +1 -0
  152. package/dist/types/ReferenceInput/ReferenceInput.d.ts +1 -0
  153. package/dist/types/ReferenceRangeEditor/ReferenceRangeEditor.d.ts +1 -0
  154. package/dist/types/RequestGroupDisplay/RequestGroupDisplay.d.ts +1 -0
  155. package/dist/types/ResourceArrayDisplay/ResourceArrayDisplay.d.ts +1 -0
  156. package/dist/types/ResourceArrayInput/ResourceArrayInput.d.ts +1 -0
  157. package/dist/types/ResourceAvatar/ResourceAvatar.d.ts +1 -0
  158. package/dist/types/ResourceBadge/ResourceBadge.d.ts +1 -0
  159. package/dist/types/ResourceBlame/ResourceBlame.d.ts +1 -0
  160. package/dist/types/ResourceDiff/ResourceDiff.d.ts +1 -0
  161. package/dist/types/ResourceDiffTable/ResourceDiffTable.d.ts +1 -0
  162. package/dist/types/ResourceForm/ResourceForm.d.ts +1 -0
  163. package/dist/types/ResourceHistoryTable/ResourceHistoryTable.d.ts +1 -0
  164. package/dist/types/ResourceInput/ResourceInput.d.ts +1 -0
  165. package/dist/types/ResourceName/ResourceName.d.ts +1 -0
  166. package/dist/types/ResourcePropertyDisplay/ResourcePropertyDisplay.d.ts +1 -0
  167. package/dist/types/ResourcePropertyInput/ResourcePropertyInput.d.ts +1 -0
  168. package/dist/types/ResourceTable/ResourceTable.d.ts +1 -0
  169. package/dist/types/ResourceTimeline/ResourceTimeline.d.ts +1 -0
  170. package/dist/types/Scheduler/Scheduler.d.ts +1 -0
  171. package/dist/types/SearchControl/SearchUtils.d.ts +1 -0
  172. package/dist/types/SearchExportDialog/SearchExportDialog.d.ts +1 -0
  173. package/dist/types/SearchFieldEditor/SearchFieldEditor.d.ts +1 -0
  174. package/dist/types/SearchFilterEditor/SearchFilterEditor.d.ts +1 -0
  175. package/dist/types/SearchFilterValueDialog/SearchFilterValueDialog.d.ts +1 -0
  176. package/dist/types/SearchFilterValueDisplay/SearchFilterValueDisplay.d.ts +1 -0
  177. package/dist/types/SearchFilterValueInput/SearchFilterValueInput.d.ts +1 -0
  178. package/dist/types/SearchPopupMenu/SearchPopupMenu.d.ts +1 -0
  179. package/dist/types/ServiceRequestTimeline/ServiceRequestTimeline.d.ts +1 -0
  180. package/dist/types/StatusBadge/StatusBadge.d.ts +1 -0
  181. package/dist/types/TimingInput/TimingInput.d.ts +1 -0
  182. package/dist/types/ValueSetAutocomplete/ValueSetAutocomplete.d.ts +1 -0
  183. package/dist/types/auth/ChooseProfileForm.d.ts +1 -0
  184. package/dist/types/auth/ChooseScopeForm.d.ts +1 -0
  185. package/dist/types/auth/MfaForm.d.ts +1 -0
  186. package/dist/types/auth/NewProjectForm.d.ts +1 -0
  187. package/package.json +13 -13
@@ -1,4 +1,4 @@
1
- import { createStyles, Navbar as Navbar$1, Text, Button, Space } from '@mantine/core';
1
+ import { createStyles, Navbar as Navbar$1, ScrollArea, Text, Button, Space } from '@mantine/core';
2
2
  import React, { useState } from 'react';
3
3
  import { useLocation, useSearchParams } from 'react-router-dom';
4
4
  import { BookmarkDialog } from '../BookmarkDialog/BookmarkDialog.mjs';
@@ -56,6 +56,9 @@ const useStyles = createStyles((theme) => {
56
56
  function Navbar(props) {
57
57
  const { classes } = useStyles();
58
58
  const navigate = useMedplumNavigate();
59
+ const location = useLocation();
60
+ const [searchParams] = useSearchParams();
61
+ const activeLink = getActiveLink(location.pathname, searchParams, props.menus);
59
62
  const [bookmarkDialogVisible, setBookmarkDialogVisible] = useState(false);
60
63
  function onLinkClick(e, to) {
61
64
  e.stopPropagation();
@@ -72,48 +75,96 @@ function Navbar(props) {
72
75
  }
73
76
  return (React.createElement(React.Fragment, null,
74
77
  React.createElement(Navbar$1, { width: { sm: 250 }, p: "xs" },
75
- React.createElement(Navbar$1.Section, { mb: "sm" },
76
- React.createElement(CodeInput, { key: window.location.pathname, name: "resourceType", placeholder: "Resource Type", property: {
77
- binding: {
78
- valueSet: 'http://hl7.org/fhir/ValueSet/resource-types',
79
- },
80
- }, onChange: (newValue) => navigateResourceType(newValue), creatable: false, maxSelectedValues: 0, clearSearchOnChange: true, clearable: false })),
81
- React.createElement(Navbar$1.Section, { grow: true },
82
- props.menus?.map((menu) => (React.createElement(React.Fragment, { key: `menu-${menu.title}` },
83
- React.createElement(Text, { className: classes.menuTitle }, menu.title),
84
- menu.links?.map((link) => (React.createElement(NavbarLink, { key: link.href, to: link.href, onClick: (e) => onLinkClick(e, link.href) },
85
- React.createElement(NavLinkIcon, { to: link.href, icon: link.icon }),
86
- React.createElement("span", null, link.label))))))),
87
- props.displayAddBookmark && (React.createElement(Button, { variant: "subtle", size: "xs", mt: "xl", leftIcon: React.createElement(IconPlus, { size: "0.75rem" }), onClick: () => setBookmarkDialogVisible(true) }, "Add Bookmark")))),
78
+ React.createElement(ScrollArea, null,
79
+ React.createElement(Navbar$1.Section, { mb: "sm" },
80
+ React.createElement(CodeInput, { key: window.location.pathname, name: "resourceType", placeholder: "Resource Type", property: {
81
+ binding: {
82
+ valueSet: 'http://hl7.org/fhir/ValueSet/resource-types',
83
+ },
84
+ }, onChange: (newValue) => navigateResourceType(newValue), creatable: false, maxSelectedValues: 0, clearSearchOnChange: true, clearable: false })),
85
+ React.createElement(Navbar$1.Section, { grow: true },
86
+ props.menus?.map((menu) => (React.createElement(React.Fragment, { key: `menu-${menu.title}` },
87
+ React.createElement(Text, { className: classes.menuTitle }, menu.title),
88
+ menu.links?.map((link) => (React.createElement(NavbarLink, { key: link.href, to: link.href, active: link.href === activeLink?.href, onClick: (e) => onLinkClick(e, link.href) },
89
+ React.createElement(NavLinkIcon, { to: link.href, icon: link.icon }),
90
+ React.createElement("span", null, link.label))))))),
91
+ props.displayAddBookmark && (React.createElement(Button, { variant: "subtle", size: "xs", mt: "xl", leftIcon: React.createElement(IconPlus, { size: "0.75rem" }), onClick: () => setBookmarkDialogVisible(true) }, "Add Bookmark"))))),
88
92
  React.createElement(BookmarkDialog, { visible: bookmarkDialogVisible, onOk: () => setBookmarkDialogVisible(false), onCancel: () => setBookmarkDialogVisible(false) })));
89
93
  }
90
94
  function NavbarLink(props) {
91
95
  const { classes, cx } = useStyles();
92
- const location = useLocation();
93
- const [searchParams] = useSearchParams();
94
- const toUrl = new URL(props.to, window.location.protocol + '//' + window.location.host);
95
- const isActive = location.pathname === toUrl.pathname && matchesParams(searchParams, toUrl);
96
- return (React.createElement(MedplumLink, { onClick: props.onClick, to: props.to, className: cx(classes.link, { [classes.linkActive]: isActive }) }, props.children));
96
+ return (React.createElement(MedplumLink, { onClick: props.onClick, to: props.to, className: cx(classes.link, { [classes.linkActive]: props.active }) }, props.children));
97
+ }
98
+ function NavLinkIcon(props) {
99
+ if (props.icon) {
100
+ return props.icon;
101
+ }
102
+ return React.createElement(Space, { w: 30 });
97
103
  }
98
104
  /**
99
- * Returns true if the search params match.
100
- * @param searchParams The current search params.
101
- * @param toUrl The destination URL of the link.
102
- * @returns True if the search params match.
105
+ * Returns the best "active" link for the menu.
106
+ * In most cases, the navbar links are simple, and an exact match can determine which link is active.
107
+ * However, we ignore some search parameters to support pagination.
108
+ * But we cannot ignore all search parameters, to support separate links based on search filters.
109
+ * So in the end, we use a simple scoring system based on the number of matching query search params.
110
+ * @param currentPathname The web browser current pathname.
111
+ * @param currentSearchParams The web browser current search parameters.
112
+ * @param menus Collection of navbar menus and links.
113
+ * @returns The active link if one is found.
103
114
  */
104
- function matchesParams(searchParams, toUrl) {
105
- for (const [key, value] of toUrl.searchParams.entries()) {
106
- if (searchParams.get(key) !== value) {
107
- return false;
115
+ function getActiveLink(currentPathname, currentSearchParams, menus) {
116
+ let bestLink = undefined;
117
+ let bestScore = 0;
118
+ if (menus) {
119
+ for (const menu of menus) {
120
+ if (menu.links) {
121
+ for (const link of menu.links) {
122
+ const score = getLinkScore(currentPathname, currentSearchParams, link.href);
123
+ if (score > bestScore) {
124
+ bestScore = score;
125
+ bestLink = link;
126
+ }
127
+ }
128
+ }
108
129
  }
109
130
  }
110
- return true;
131
+ return bestLink;
111
132
  }
112
- function NavLinkIcon(props) {
113
- if (props.icon) {
114
- return props.icon;
133
+ /**
134
+ * Calculates a score for a link.
135
+ * Zero means "does not match at all".
136
+ * One means "matches the pathname only".
137
+ * Additional increases for each matching search parameter.
138
+ * Ignores pagination parameters "_count" and "_offset".
139
+ * @param currentPathname The web browser current pathname.
140
+ * @param currentSearchParams The web browser current search parameters.
141
+ * @param linkHref A candidate link href.
142
+ * @returns The link score.
143
+ */
144
+ function getLinkScore(currentPathname, currentSearchParams, linkHref) {
145
+ const linkUrl = new URL(linkHref, 'https://example.com');
146
+ if (currentPathname !== linkUrl.pathname) {
147
+ return 0;
115
148
  }
116
- return React.createElement(Space, { w: 30 });
149
+ const ignoredParams = ['_count', '_offset'];
150
+ for (const [key, value] of linkUrl.searchParams.entries()) {
151
+ if (ignoredParams.includes(key)) {
152
+ continue;
153
+ }
154
+ if (currentSearchParams.get(key) !== value) {
155
+ return 0;
156
+ }
157
+ }
158
+ let count = 1;
159
+ for (const [key, value] of currentSearchParams.entries()) {
160
+ if (ignoredParams.includes(key)) {
161
+ continue;
162
+ }
163
+ if (linkUrl.searchParams.get(key) === value) {
164
+ count++;
165
+ }
166
+ }
167
+ return count;
117
168
  }
118
169
 
119
170
  export { Navbar };
@@ -1 +1 @@
1
- {"version":3,"file":"Navbar.mjs","sources":["../../../src/AppShell/Navbar.tsx"],"sourcesContent":["import { Button, createStyles, Navbar as MantineNavbar, Space, Text } from '@mantine/core';\nimport { IconPlus } from '@tabler/icons-react';\nimport React, { useState } from 'react';\nimport { useLocation, useSearchParams } from 'react-router-dom';\nimport { BookmarkDialog } from '../BookmarkDialog/BookmarkDialog';\nimport { CodeInput } from '../CodeInput/CodeInput';\nimport { MedplumLink } from '../MedplumLink/MedplumLink';\nimport { useMedplumNavigate } from '../MedplumProvider/MedplumProvider';\n\nconst useStyles = createStyles((theme) => {\n return {\n menuTitle: {\n margin: '20px 0 4px 6px',\n fontSize: '9px',\n fontWeight: 'normal',\n textTransform: 'uppercase',\n letterSpacing: '2px',\n },\n\n link: {\n ...theme.fn.focusStyles(),\n display: 'flex',\n alignItems: 'center',\n textDecoration: 'none',\n fontSize: theme.fontSizes.sm,\n color: theme.colorScheme === 'dark' ? theme.colors.dark[1] : theme.colors.gray[7],\n padding: `8px 12px`,\n borderRadius: theme.radius.sm,\n fontWeight: 500,\n\n '&:hover': {\n backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[6] : theme.colors.gray[0],\n color: theme.colorScheme === 'dark' ? theme.white : theme.black,\n textDecoration: 'none',\n\n [`& svg`]: {\n color: theme.colorScheme === 'dark' ? theme.white : theme.black,\n },\n },\n\n '& svg': {\n color: theme.colorScheme === 'dark' ? theme.colors.dark[2] : theme.colors.gray[6],\n marginRight: theme.spacing.sm,\n strokeWidth: 1.5,\n width: 18,\n height: 18,\n },\n },\n\n linkActive: {\n '&, &:hover': {\n backgroundColor: theme.fn.variant({ variant: 'light', color: theme.primaryColor }).background,\n color: theme.fn.variant({ variant: 'light', color: theme.primaryColor }).color,\n [`& svg`]: {\n color: theme.fn.variant({ variant: 'light', color: theme.primaryColor }).color,\n },\n },\n },\n };\n});\n\nexport interface NavbarLink {\n icon?: JSX.Element;\n label?: string;\n href: string;\n}\n\nexport interface NavbarMenu {\n title?: string;\n links?: NavbarLink[];\n}\n\nexport interface NavbarProps {\n menus?: NavbarMenu[];\n closeNavbar: () => void;\n displayAddBookmark?: boolean;\n}\n\nexport function Navbar(props: NavbarProps): JSX.Element {\n const { classes } = useStyles();\n const navigate = useMedplumNavigate();\n const [bookmarkDialogVisible, setBookmarkDialogVisible] = useState(false);\n\n function onLinkClick(e: React.SyntheticEvent, to: string): void {\n e.stopPropagation();\n e.preventDefault();\n navigate(to);\n if (window.innerWidth < 768) {\n props.closeNavbar();\n }\n }\n\n function navigateResourceType(resourceType: string | undefined): void {\n if (resourceType) {\n navigate(`/${resourceType}`);\n }\n }\n\n return (\n <>\n <MantineNavbar width={{ sm: 250 }} p=\"xs\">\n <MantineNavbar.Section mb=\"sm\">\n <CodeInput\n key={window.location.pathname}\n name=\"resourceType\"\n placeholder=\"Resource Type\"\n property={{\n binding: {\n valueSet: 'http://hl7.org/fhir/ValueSet/resource-types',\n },\n }}\n onChange={(newValue) => navigateResourceType(newValue)}\n creatable={false}\n maxSelectedValues={0}\n clearSearchOnChange={true}\n clearable={false}\n />\n </MantineNavbar.Section>\n <MantineNavbar.Section grow>\n {props.menus?.map((menu) => (\n <React.Fragment key={`menu-${menu.title}`}>\n <Text className={classes.menuTitle}>{menu.title}</Text>\n {menu.links?.map((link) => (\n <NavbarLink key={link.href} to={link.href} onClick={(e) => onLinkClick(e, link.href)}>\n <NavLinkIcon to={link.href} icon={link.icon} />\n <span>{link.label}</span>\n </NavbarLink>\n ))}\n </React.Fragment>\n ))}\n {props.displayAddBookmark && (\n <Button\n variant=\"subtle\"\n size=\"xs\"\n mt=\"xl\"\n leftIcon={<IconPlus size=\"0.75rem\" />}\n onClick={() => setBookmarkDialogVisible(true)}\n >\n Add Bookmark\n </Button>\n )}\n </MantineNavbar.Section>\n </MantineNavbar>\n <BookmarkDialog\n visible={bookmarkDialogVisible}\n onOk={() => setBookmarkDialogVisible(false)}\n onCancel={() => setBookmarkDialogVisible(false)}\n />\n </>\n );\n}\ninterface NavbarLinkProps {\n to: string;\n onClick: React.MouseEventHandler;\n children: React.ReactNode;\n}\n\nfunction NavbarLink(props: NavbarLinkProps): JSX.Element {\n const { classes, cx } = useStyles();\n const location = useLocation();\n const [searchParams] = useSearchParams();\n const toUrl = new URL(props.to, window.location.protocol + '//' + window.location.host);\n const isActive = location.pathname === toUrl.pathname && matchesParams(searchParams, toUrl);\n\n return (\n <MedplumLink onClick={props.onClick} to={props.to} className={cx(classes.link, { [classes.linkActive]: isActive })}>\n {props.children}\n </MedplumLink>\n );\n}\n\n/**\n * Returns true if the search params match.\n * @param searchParams The current search params.\n * @param toUrl The destination URL of the link.\n * @returns True if the search params match.\n */\nfunction matchesParams(searchParams: URLSearchParams, toUrl: URL): boolean {\n for (const [key, value] of toUrl.searchParams.entries()) {\n if (searchParams.get(key) !== value) {\n return false;\n }\n }\n return true;\n}\n\ninterface NavLinkIconProps {\n to: string;\n icon?: JSX.Element;\n}\n\nfunction NavLinkIcon(props: NavLinkIconProps): JSX.Element {\n if (props.icon) {\n return props.icon;\n }\n return <Space w={30} />;\n}\n"],"names":["MantineNavbar"],"mappings":"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"}
1
+ {"version":3,"file":"Navbar.mjs","sources":["../../../src/AppShell/Navbar.tsx"],"sourcesContent":["import { Button, createStyles, Navbar as MantineNavbar, ScrollArea, Space, Text } from '@mantine/core';\nimport { IconPlus } from '@tabler/icons-react';\nimport React, { useState } from 'react';\nimport { useLocation, useSearchParams } from 'react-router-dom';\nimport { BookmarkDialog } from '../BookmarkDialog/BookmarkDialog';\nimport { CodeInput } from '../CodeInput/CodeInput';\nimport { MedplumLink } from '../MedplumLink/MedplumLink';\nimport { useMedplumNavigate } from '../MedplumProvider/MedplumProvider';\n\nconst useStyles = createStyles((theme) => {\n return {\n menuTitle: {\n margin: '20px 0 4px 6px',\n fontSize: '9px',\n fontWeight: 'normal',\n textTransform: 'uppercase',\n letterSpacing: '2px',\n },\n\n link: {\n ...theme.fn.focusStyles(),\n display: 'flex',\n alignItems: 'center',\n textDecoration: 'none',\n fontSize: theme.fontSizes.sm,\n color: theme.colorScheme === 'dark' ? theme.colors.dark[1] : theme.colors.gray[7],\n padding: `8px 12px`,\n borderRadius: theme.radius.sm,\n fontWeight: 500,\n\n '&:hover': {\n backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[6] : theme.colors.gray[0],\n color: theme.colorScheme === 'dark' ? theme.white : theme.black,\n textDecoration: 'none',\n\n [`& svg`]: {\n color: theme.colorScheme === 'dark' ? theme.white : theme.black,\n },\n },\n\n '& svg': {\n color: theme.colorScheme === 'dark' ? theme.colors.dark[2] : theme.colors.gray[6],\n marginRight: theme.spacing.sm,\n strokeWidth: 1.5,\n width: 18,\n height: 18,\n },\n },\n\n linkActive: {\n '&, &:hover': {\n backgroundColor: theme.fn.variant({ variant: 'light', color: theme.primaryColor }).background,\n color: theme.fn.variant({ variant: 'light', color: theme.primaryColor }).color,\n [`& svg`]: {\n color: theme.fn.variant({ variant: 'light', color: theme.primaryColor }).color,\n },\n },\n },\n };\n});\n\nexport interface NavbarLink {\n icon?: JSX.Element;\n label?: string;\n href: string;\n}\n\nexport interface NavbarMenu {\n title?: string;\n links?: NavbarLink[];\n}\n\nexport interface NavbarProps {\n menus?: NavbarMenu[];\n closeNavbar: () => void;\n displayAddBookmark?: boolean;\n}\n\nexport function Navbar(props: NavbarProps): JSX.Element {\n const { classes } = useStyles();\n const navigate = useMedplumNavigate();\n const location = useLocation();\n const [searchParams] = useSearchParams();\n const activeLink = getActiveLink(location.pathname, searchParams, props.menus);\n const [bookmarkDialogVisible, setBookmarkDialogVisible] = useState(false);\n\n function onLinkClick(e: React.SyntheticEvent, to: string): void {\n e.stopPropagation();\n e.preventDefault();\n navigate(to);\n if (window.innerWidth < 768) {\n props.closeNavbar();\n }\n }\n\n function navigateResourceType(resourceType: string | undefined): void {\n if (resourceType) {\n navigate(`/${resourceType}`);\n }\n }\n\n return (\n <>\n <MantineNavbar width={{ sm: 250 }} p=\"xs\">\n <ScrollArea>\n <MantineNavbar.Section mb=\"sm\">\n <CodeInput\n key={window.location.pathname}\n name=\"resourceType\"\n placeholder=\"Resource Type\"\n property={{\n binding: {\n valueSet: 'http://hl7.org/fhir/ValueSet/resource-types',\n },\n }}\n onChange={(newValue) => navigateResourceType(newValue)}\n creatable={false}\n maxSelectedValues={0}\n clearSearchOnChange={true}\n clearable={false}\n />\n </MantineNavbar.Section>\n <MantineNavbar.Section grow>\n {props.menus?.map((menu) => (\n <React.Fragment key={`menu-${menu.title}`}>\n <Text className={classes.menuTitle}>{menu.title}</Text>\n {menu.links?.map((link) => (\n <NavbarLink\n key={link.href}\n to={link.href}\n active={link.href === activeLink?.href}\n onClick={(e) => onLinkClick(e, link.href)}\n >\n <NavLinkIcon to={link.href} icon={link.icon} />\n <span>{link.label}</span>\n </NavbarLink>\n ))}\n </React.Fragment>\n ))}\n {props.displayAddBookmark && (\n <Button\n variant=\"subtle\"\n size=\"xs\"\n mt=\"xl\"\n leftIcon={<IconPlus size=\"0.75rem\" />}\n onClick={() => setBookmarkDialogVisible(true)}\n >\n Add Bookmark\n </Button>\n )}\n </MantineNavbar.Section>\n </ScrollArea>\n </MantineNavbar>\n <BookmarkDialog\n visible={bookmarkDialogVisible}\n onOk={() => setBookmarkDialogVisible(false)}\n onCancel={() => setBookmarkDialogVisible(false)}\n />\n </>\n );\n}\n\ninterface NavbarLinkProps {\n to: string;\n active: boolean;\n onClick: React.MouseEventHandler;\n children: React.ReactNode;\n}\n\nfunction NavbarLink(props: NavbarLinkProps): JSX.Element {\n const { classes, cx } = useStyles();\n return (\n <MedplumLink\n onClick={props.onClick}\n to={props.to}\n className={cx(classes.link, { [classes.linkActive]: props.active })}\n >\n {props.children}\n </MedplumLink>\n );\n}\n\ninterface NavLinkIconProps {\n to: string;\n icon?: JSX.Element;\n}\n\nfunction NavLinkIcon(props: NavLinkIconProps): JSX.Element {\n if (props.icon) {\n return props.icon;\n }\n return <Space w={30} />;\n}\n\n/**\n * Returns the best \"active\" link for the menu.\n * In most cases, the navbar links are simple, and an exact match can determine which link is active.\n * However, we ignore some search parameters to support pagination.\n * But we cannot ignore all search parameters, to support separate links based on search filters.\n * So in the end, we use a simple scoring system based on the number of matching query search params.\n * @param currentPathname The web browser current pathname.\n * @param currentSearchParams The web browser current search parameters.\n * @param menus Collection of navbar menus and links.\n * @returns The active link if one is found.\n */\nfunction getActiveLink(\n currentPathname: string,\n currentSearchParams: URLSearchParams,\n menus: NavbarMenu[] | undefined\n): NavbarLink | undefined {\n let bestLink = undefined;\n let bestScore = 0;\n\n if (menus) {\n for (const menu of menus) {\n if (menu.links) {\n for (const link of menu.links) {\n const score = getLinkScore(currentPathname, currentSearchParams, link.href);\n if (score > bestScore) {\n bestScore = score;\n bestLink = link;\n }\n }\n }\n }\n }\n\n return bestLink;\n}\n\n/**\n * Calculates a score for a link.\n * Zero means \"does not match at all\".\n * One means \"matches the pathname only\".\n * Additional increases for each matching search parameter.\n * Ignores pagination parameters \"_count\" and \"_offset\".\n * @param currentPathname The web browser current pathname.\n * @param currentSearchParams The web browser current search parameters.\n * @param linkHref A candidate link href.\n * @returns The link score.\n */\nfunction getLinkScore(currentPathname: string, currentSearchParams: URLSearchParams, linkHref: string): number {\n const linkUrl = new URL(linkHref, 'https://example.com');\n if (currentPathname !== linkUrl.pathname) {\n return 0;\n }\n const ignoredParams = ['_count', '_offset'];\n for (const [key, value] of linkUrl.searchParams.entries()) {\n if (ignoredParams.includes(key)) {\n continue;\n }\n if (currentSearchParams.get(key) !== value) {\n return 0;\n }\n }\n let count = 1;\n for (const [key, value] of currentSearchParams.entries()) {\n if (ignoredParams.includes(key)) {\n continue;\n }\n if (linkUrl.searchParams.get(key) === value) {\n count++;\n }\n }\n return count;\n}\n"],"names":["MantineNavbar"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { globalSchema, getSearchParameterDetails, SearchParameterType, formatDateTime } from '@medplum/core';
1
+ import { globalSchema, Operator, getSearchParameterDetails, SearchParameterType, formatDateTime } from '@medplum/core';
2
2
  import React from 'react';
3
3
  import { ResourceName } from '../ResourceName/ResourceName.mjs';
4
4
 
@@ -6,7 +6,8 @@ function SearchFilterValueDisplay(props) {
6
6
  const { resourceType, filter } = props;
7
7
  const searchParam = globalSchema.types[resourceType]?.searchParams?.[filter.code];
8
8
  if (searchParam) {
9
- if (searchParam.type === 'reference') {
9
+ if (searchParam.type === 'reference' &&
10
+ (filter.operator === Operator.EQUALS || filter.operator === Operator.NOT_EQUALS)) {
10
11
  return React.createElement(ResourceName, { value: { reference: filter.value } });
11
12
  }
12
13
  const searchParamDetails = getSearchParameterDetails(resourceType, searchParam);
@@ -1 +1 @@
1
- {"version":3,"file":"SearchFilterValueDisplay.mjs","sources":["../../../src/SearchFilterValueDisplay/SearchFilterValueDisplay.tsx"],"sourcesContent":["import { Filter, formatDateTime, getSearchParameterDetails, globalSchema, SearchParameterType } from '@medplum/core';\nimport React from 'react';\nimport { ResourceName } from '../ResourceName/ResourceName';\n\nexport interface SearchFilterValueDisplayProps {\n readonly resourceType: string;\n readonly filter: Filter;\n}\n\nexport function SearchFilterValueDisplay(props: SearchFilterValueDisplayProps): JSX.Element {\n const { resourceType, filter } = props;\n\n const searchParam = globalSchema.types[resourceType]?.searchParams?.[filter.code];\n if (searchParam) {\n if (searchParam.type === 'reference') {\n return <ResourceName value={{ reference: filter.value }} />;\n }\n\n const searchParamDetails = getSearchParameterDetails(resourceType, searchParam);\n if (filter.code === '_lastUpdated' || searchParamDetails.type === SearchParameterType.DATETIME) {\n return <>{formatDateTime(filter.value)}</>;\n }\n }\n\n return <>{filter.value}</>;\n}\n"],"names":[],"mappings":";;;;AASM,SAAU,wBAAwB,CAAC,KAAoC,EAAA;AAC3E,IAAA,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;AAEvC,IAAA,MAAM,WAAW,GAAG,YAAY,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;AAClF,IAAA,IAAI,WAAW,EAAE;AACf,QAAA,IAAI,WAAW,CAAC,IAAI,KAAK,WAAW,EAAE;AACpC,YAAA,OAAO,KAAC,CAAA,aAAA,CAAA,YAAY,EAAC,EAAA,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,KAAK,EAAE,GAAI,CAAC;AAC7D,SAAA;QAED,MAAM,kBAAkB,GAAG,yBAAyB,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC;AAChF,QAAA,IAAI,MAAM,CAAC,IAAI,KAAK,cAAc,IAAI,kBAAkB,CAAC,IAAI,KAAK,mBAAmB,CAAC,QAAQ,EAAE;YAC9F,OAAO,KAAA,CAAA,aAAA,CAAA,KAAA,CAAA,QAAA,EAAA,IAAA,EAAG,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,CAAI,CAAC;AAC5C,SAAA;AACF,KAAA;AAED,IAAA,OAAO,KAAG,CAAA,aAAA,CAAA,KAAA,CAAA,QAAA,EAAA,IAAA,EAAA,MAAM,CAAC,KAAK,CAAI,CAAC;AAC7B;;;;"}
1
+ {"version":3,"file":"SearchFilterValueDisplay.mjs","sources":["../../../src/SearchFilterValueDisplay/SearchFilterValueDisplay.tsx"],"sourcesContent":["import {\n Filter,\n formatDateTime,\n getSearchParameterDetails,\n globalSchema,\n Operator,\n SearchParameterType,\n} from '@medplum/core';\nimport React from 'react';\nimport { ResourceName } from '../ResourceName/ResourceName';\n\nexport interface SearchFilterValueDisplayProps {\n readonly resourceType: string;\n readonly filter: Filter;\n}\n\nexport function SearchFilterValueDisplay(props: SearchFilterValueDisplayProps): JSX.Element {\n const { resourceType, filter } = props;\n\n const searchParam = globalSchema.types[resourceType]?.searchParams?.[filter.code];\n if (searchParam) {\n if (\n searchParam.type === 'reference' &&\n (filter.operator === Operator.EQUALS || filter.operator === Operator.NOT_EQUALS)\n ) {\n return <ResourceName value={{ reference: filter.value }} />;\n }\n\n const searchParamDetails = getSearchParameterDetails(resourceType, searchParam);\n if (filter.code === '_lastUpdated' || searchParamDetails.type === SearchParameterType.DATETIME) {\n return <>{formatDateTime(filter.value)}</>;\n }\n }\n\n return <>{filter.value}</>;\n}\n"],"names":[],"mappings":";;;;AAgBM,SAAU,wBAAwB,CAAC,KAAoC,EAAA;AAC3E,IAAA,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;AAEvC,IAAA,MAAM,WAAW,GAAG,YAAY,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;AAClF,IAAA,IAAI,WAAW,EAAE;AACf,QAAA,IACE,WAAW,CAAC,IAAI,KAAK,WAAW;AAChC,aAAC,MAAM,CAAC,QAAQ,KAAK,QAAQ,CAAC,MAAM,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,CAAC,UAAU,CAAC,EAChF;AACA,YAAA,OAAO,KAAC,CAAA,aAAA,CAAA,YAAY,EAAC,EAAA,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,KAAK,EAAE,GAAI,CAAC;AAC7D,SAAA;QAED,MAAM,kBAAkB,GAAG,yBAAyB,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC;AAChF,QAAA,IAAI,MAAM,CAAC,IAAI,KAAK,cAAc,IAAI,kBAAkB,CAAC,IAAI,KAAK,mBAAmB,CAAC,QAAQ,EAAE;YAC9F,OAAO,KAAA,CAAA,aAAA,CAAA,KAAA,CAAA,QAAA,EAAA,IAAA,EAAG,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,CAAI,CAAC;AAC5C,SAAA;AACF,KAAA;AAED,IAAA,OAAO,KAAG,CAAA,aAAA,CAAA,KAAA,CAAA,QAAA,EAAA,IAAA,EAAA,MAAM,CAAC,KAAK,CAAI,CAAC;AAC7B;;;;"}
@@ -25,6 +25,9 @@ function ValueSetAutocomplete(props) {
25
25
  const medplum = useMedplum();
26
26
  const { elementDefinition, creatable, clearable, ...rest } = props;
27
27
  const loadValues = useCallback(async (input, signal) => {
28
+ if (!elementDefinition.binding) {
29
+ return [];
30
+ }
28
31
  const system = elementDefinition.binding?.valueSet;
29
32
  const valueSet = await medplum.searchValueSet(system, input, { signal });
30
33
  const valueSetElements = valueSet.expansion?.contains;
@@ -1 +1 @@
1
- {"version":3,"file":"ValueSetAutocomplete.mjs","sources":["../../../src/ValueSetAutocomplete/ValueSetAutocomplete.tsx"],"sourcesContent":["import { ElementDefinition, ValueSetExpansionContains } from '@medplum/fhirtypes';\nimport React, { useCallback } from 'react';\nimport {\n AsyncAutocomplete,\n AsyncAutocompleteOption,\n AsyncAutocompleteProps,\n} from '../AsyncAutocomplete/AsyncAutocomplete';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider';\n\nexport interface ValueSetAutocompleteProps\n extends Omit<AsyncAutocompleteProps<ValueSetExpansionContains>, 'loadOptions' | 'toKey' | 'toOption'> {\n elementDefinition: ElementDefinition;\n creatable?: boolean;\n clearable?: boolean;\n}\n\nfunction toKey(element: ValueSetExpansionContains): string {\n return element.code as string;\n}\n\nfunction toOption(element: ValueSetExpansionContains): AsyncAutocompleteOption<ValueSetExpansionContains> {\n return {\n value: element.code as string,\n label: getDisplay(element),\n resource: element,\n };\n}\n\nfunction createValue(input: string): ValueSetExpansionContains {\n return {\n code: input,\n display: input,\n };\n}\n\n/**\n * A low-level component to autocomplete based on a FHIR Valueset.\n */\nexport function ValueSetAutocomplete(props: ValueSetAutocompleteProps): JSX.Element {\n const medplum = useMedplum();\n const { elementDefinition, creatable, clearable, ...rest } = props;\n\n const loadValues = useCallback(\n async (input: string, signal: AbortSignal): Promise<ValueSetExpansionContains[]> => {\n const system = elementDefinition.binding?.valueSet as string;\n const valueSet = await medplum.searchValueSet(system, input, { signal });\n const valueSetElements = valueSet.expansion?.contains as ValueSetExpansionContains[];\n const newData: ValueSetExpansionContains[] = [];\n for (const valueSetElement of valueSetElements) {\n if (valueSetElement.code && !newData.some((item) => item.code === valueSetElement.code)) {\n newData.push(valueSetElement);\n }\n }\n\n return newData;\n },\n [medplum, elementDefinition]\n );\n\n return (\n <AsyncAutocomplete\n {...rest}\n creatable={creatable ?? true}\n clearable={clearable ?? true}\n toKey={toKey}\n toOption={toOption}\n loadOptions={loadValues}\n onCreate={createValue}\n getCreateLabel={creatable === false ? undefined : (query: any) => `+ Create ${query}`}\n />\n );\n}\n\nfunction getDisplay(item: ValueSetExpansionContains): string {\n return item.display || item.code || '';\n}\n"],"names":[],"mappings":";;;;AAgBA,SAAS,KAAK,CAAC,OAAkC,EAAA;IAC/C,OAAO,OAAO,CAAC,IAAc,CAAC;AAChC,CAAC;AAED,SAAS,QAAQ,CAAC,OAAkC,EAAA;IAClD,OAAO;QACL,KAAK,EAAE,OAAO,CAAC,IAAc;AAC7B,QAAA,KAAK,EAAE,UAAU,CAAC,OAAO,CAAC;AAC1B,QAAA,QAAQ,EAAE,OAAO;KAClB,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,KAAa,EAAA;IAChC,OAAO;AACL,QAAA,IAAI,EAAE,KAAK;AACX,QAAA,OAAO,EAAE,KAAK;KACf,CAAC;AACJ,CAAC;AAED;;AAEG;AACG,SAAU,oBAAoB,CAAC,KAAgC,EAAA;AACnE,IAAA,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;AAC7B,IAAA,MAAM,EAAE,iBAAiB,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,CAAC;IAEnE,MAAM,UAAU,GAAG,WAAW,CAC5B,OAAO,KAAa,EAAE,MAAmB,KAA0C;AACjF,QAAA,MAAM,MAAM,GAAG,iBAAiB,CAAC,OAAO,EAAE,QAAkB,CAAC;AAC7D,QAAA,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,cAAc,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;AACzE,QAAA,MAAM,gBAAgB,GAAG,QAAQ,CAAC,SAAS,EAAE,QAAuC,CAAC;QACrF,MAAM,OAAO,GAAgC,EAAE,CAAC;AAChD,QAAA,KAAK,MAAM,eAAe,IAAI,gBAAgB,EAAE;YAC9C,IAAI,eAAe,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,eAAe,CAAC,IAAI,CAAC,EAAE;AACvF,gBAAA,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;AAC/B,aAAA;AACF,SAAA;AAED,QAAA,OAAO,OAAO,CAAC;AACjB,KAAC,EACD,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAC7B,CAAC;IAEF,QACE,KAAC,CAAA,aAAA,CAAA,iBAAiB,EACZ,EAAA,GAAA,IAAI,EACR,SAAS,EAAE,SAAS,IAAI,IAAI,EAC5B,SAAS,EAAE,SAAS,IAAI,IAAI,EAC5B,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,QAAQ,EAClB,WAAW,EAAE,UAAU,EACvB,QAAQ,EAAE,WAAW,EACrB,cAAc,EAAE,SAAS,KAAK,KAAK,GAAG,SAAS,GAAG,CAAC,KAAU,KAAK,CAAA,SAAA,EAAY,KAAK,CAAA,CAAE,EACrF,CAAA,EACF;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,IAA+B,EAAA;IACjD,OAAO,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;AACzC;;;;"}
1
+ {"version":3,"file":"ValueSetAutocomplete.mjs","sources":["../../../src/ValueSetAutocomplete/ValueSetAutocomplete.tsx"],"sourcesContent":["import { ElementDefinition, ValueSetExpansionContains } from '@medplum/fhirtypes';\nimport React, { useCallback } from 'react';\nimport {\n AsyncAutocomplete,\n AsyncAutocompleteOption,\n AsyncAutocompleteProps,\n} from '../AsyncAutocomplete/AsyncAutocomplete';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider';\n\nexport interface ValueSetAutocompleteProps\n extends Omit<AsyncAutocompleteProps<ValueSetExpansionContains>, 'loadOptions' | 'toKey' | 'toOption'> {\n elementDefinition: ElementDefinition;\n creatable?: boolean;\n clearable?: boolean;\n}\n\nfunction toKey(element: ValueSetExpansionContains): string {\n return element.code as string;\n}\n\nfunction toOption(element: ValueSetExpansionContains): AsyncAutocompleteOption<ValueSetExpansionContains> {\n return {\n value: element.code as string,\n label: getDisplay(element),\n resource: element,\n };\n}\n\nfunction createValue(input: string): ValueSetExpansionContains {\n return {\n code: input,\n display: input,\n };\n}\n\n/**\n * A low-level component to autocomplete based on a FHIR Valueset.\n */\nexport function ValueSetAutocomplete(props: ValueSetAutocompleteProps): JSX.Element {\n const medplum = useMedplum();\n const { elementDefinition, creatable, clearable, ...rest } = props;\n\n const loadValues = useCallback(\n async (input: string, signal: AbortSignal): Promise<ValueSetExpansionContains[]> => {\n if (!elementDefinition.binding) {\n return [];\n }\n const system = elementDefinition.binding?.valueSet as string;\n const valueSet = await medplum.searchValueSet(system, input, { signal });\n const valueSetElements = valueSet.expansion?.contains as ValueSetExpansionContains[];\n const newData: ValueSetExpansionContains[] = [];\n for (const valueSetElement of valueSetElements) {\n if (valueSetElement.code && !newData.some((item) => item.code === valueSetElement.code)) {\n newData.push(valueSetElement);\n }\n }\n\n return newData;\n },\n [medplum, elementDefinition]\n );\n\n return (\n <AsyncAutocomplete\n {...rest}\n creatable={creatable ?? true}\n clearable={clearable ?? true}\n toKey={toKey}\n toOption={toOption}\n loadOptions={loadValues}\n onCreate={createValue}\n getCreateLabel={creatable === false ? undefined : (query: any) => `+ Create ${query}`}\n />\n );\n}\n\nfunction getDisplay(item: ValueSetExpansionContains): string {\n return item.display || item.code || '';\n}\n"],"names":[],"mappings":";;;;AAgBA,SAAS,KAAK,CAAC,OAAkC,EAAA;IAC/C,OAAO,OAAO,CAAC,IAAc,CAAC;AAChC,CAAC;AAED,SAAS,QAAQ,CAAC,OAAkC,EAAA;IAClD,OAAO;QACL,KAAK,EAAE,OAAO,CAAC,IAAc;AAC7B,QAAA,KAAK,EAAE,UAAU,CAAC,OAAO,CAAC;AAC1B,QAAA,QAAQ,EAAE,OAAO;KAClB,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,KAAa,EAAA;IAChC,OAAO;AACL,QAAA,IAAI,EAAE,KAAK;AACX,QAAA,OAAO,EAAE,KAAK;KACf,CAAC;AACJ,CAAC;AAED;;AAEG;AACG,SAAU,oBAAoB,CAAC,KAAgC,EAAA;AACnE,IAAA,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;AAC7B,IAAA,MAAM,EAAE,iBAAiB,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,CAAC;IAEnE,MAAM,UAAU,GAAG,WAAW,CAC5B,OAAO,KAAa,EAAE,MAAmB,KAA0C;AACjF,QAAA,IAAI,CAAC,iBAAiB,CAAC,OAAO,EAAE;AAC9B,YAAA,OAAO,EAAE,CAAC;AACX,SAAA;AACD,QAAA,MAAM,MAAM,GAAG,iBAAiB,CAAC,OAAO,EAAE,QAAkB,CAAC;AAC7D,QAAA,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,cAAc,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;AACzE,QAAA,MAAM,gBAAgB,GAAG,QAAQ,CAAC,SAAS,EAAE,QAAuC,CAAC;QACrF,MAAM,OAAO,GAAgC,EAAE,CAAC;AAChD,QAAA,KAAK,MAAM,eAAe,IAAI,gBAAgB,EAAE;YAC9C,IAAI,eAAe,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,eAAe,CAAC,IAAI,CAAC,EAAE;AACvF,gBAAA,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;AAC/B,aAAA;AACF,SAAA;AAED,QAAA,OAAO,OAAO,CAAC;AACjB,KAAC,EACD,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAC7B,CAAC;IAEF,QACE,KAAC,CAAA,aAAA,CAAA,iBAAiB,EACZ,EAAA,GAAA,IAAI,EACR,SAAS,EAAE,SAAS,IAAI,IAAI,EAC5B,SAAS,EAAE,SAAS,IAAI,IAAI,EAC5B,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,QAAQ,EAClB,WAAW,EAAE,UAAU,EACvB,QAAQ,EAAE,WAAW,EACrB,cAAc,EAAE,SAAS,KAAK,KAAK,GAAG,SAAS,GAAG,CAAC,KAAU,KAAK,CAAA,SAAA,EAAY,KAAK,CAAA,CAAE,EACrF,CAAA,EACF;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,IAA+B,EAAA;IACjD,OAAO,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;AACzC;;;;"}