@salesforce/templates 66.14.0 → 66.15.0

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 (182) hide show
  1. package/lib/generators/uiEmbeddingGenerator.js +6 -6
  2. package/lib/generators/uiEmbeddingGenerator.js.map +1 -1
  3. package/lib/i18n/i18n.d.ts +2 -2
  4. package/lib/i18n/i18n.js +2 -2
  5. package/lib/i18n/i18n.js.map +1 -1
  6. package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/package-lock.json +446 -344
  7. package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/package-lock.json +449 -347
  8. package/lib/templates/project/reactexternalapp/CHANGELOG.md +437 -0
  9. package/lib/templates/project/reactexternalapp/README.md +2 -0
  10. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/package-lock.json +675 -428
  11. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/package.json +6 -4
  12. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/patches/aria-hidden+1.2.6.patch +45 -0
  13. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/pages/Login.tsx +3 -2
  14. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/pages/Register.tsx +3 -2
  15. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/api/searchableContentTypesService.ts +9 -6
  16. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/cmsQueryFragment.ts +14 -3
  17. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/contentTypeUtils.ts +7 -5
  18. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/index.ts +1 -0
  19. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/searchChannel.ts +17 -1
  20. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/types.ts +2 -0
  21. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/MergedSearchResults.tsx +16 -4
  22. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SearchResults.tsx +1 -0
  23. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SourceSection.tsx +27 -4
  24. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/hooks/useSearch.ts +12 -7
  25. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/filterUtils.ts +14 -4
  26. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/appLayout.tsx +42 -7
  27. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/ObjectBreadcrumb.tsx +1 -5
  28. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/AccountObjectDetailPage.tsx +34 -47
  29. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/Home.tsx +3 -2
  30. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/utils/accountFields.ts +1 -14
  31. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/tsconfig.tsbuildinfo +1 -1
  32. package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleAuthUtils_Test.cls +141 -0
  33. package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleAuthUtils_Test.cls-meta.xml +5 -0
  34. package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleSocialLoginConfig.cls +23 -11
  35. package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleSocialLoginConfig_Test.cls +80 -0
  36. package/lib/templates/project/reactexternalapp/package.json +1 -1
  37. package/lib/templates/project/reactexternalapp/scripts/org-setup-config-schema.mjs +14 -2
  38. package/lib/templates/project/reactexternalapp/scripts/org-setup-dev.mjs +27 -0
  39. package/lib/templates/project/reactexternalapp/scripts/org-setup-url.mjs +108 -0
  40. package/lib/templates/project/reactexternalapp/scripts/org-setup-xml.mjs +96 -0
  41. package/lib/templates/project/reactexternalapp/scripts/org-setup.mjs +195 -3
  42. package/lib/templates/project/reactinternalapp/CHANGELOG.md +533 -0
  43. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/package-lock.json +676 -429
  44. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/package.json +7 -5
  45. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/patches/aria-hidden+1.2.6.patch +45 -0
  46. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/api/searchableContentTypesService.ts +9 -6
  47. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/cmsQueryFragment.ts +14 -3
  48. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/contentTypeUtils.ts +7 -5
  49. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/index.ts +1 -0
  50. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/searchChannel.ts +17 -1
  51. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/types.ts +2 -0
  52. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/MergedSearchResults.tsx +16 -4
  53. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SearchResults.tsx +1 -0
  54. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SourceSection.tsx +27 -4
  55. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/DefaultFilterPanel.tsx +7 -1
  56. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/hooks/useSearch.ts +12 -7
  57. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/filterUtils.ts +24 -5
  58. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/appLayout.tsx +41 -7
  59. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/ObjectBreadcrumb.tsx +1 -5
  60. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/pages/AccountObjectDetailPage.tsx +40 -50
  61. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/pages/Home.tsx +7 -2
  62. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/utils/accountFields.ts +1 -14
  63. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/tsconfig.tsbuildinfo +1 -1
  64. package/lib/templates/project/reactinternalapp/package.json +1 -1
  65. package/lib/templates/project/reactinternalapp/scripts/org-setup-config-schema.mjs +14 -2
  66. package/lib/templates/project/reactinternalapp/scripts/org-setup-dev.mjs +27 -0
  67. package/lib/templates/project/reactinternalapp/scripts/org-setup-url.mjs +108 -0
  68. package/lib/templates/project/reactinternalapp/scripts/org-setup-xml.mjs +96 -0
  69. package/lib/templates/project/reactinternalapp/scripts/org-setup.mjs +195 -3
  70. package/lib/templates/uiBundles/angularbasic/package-lock.json +446 -344
  71. package/lib/templates/uiBundles/reactbasic/package-lock.json +672 -425
  72. package/lib/templates/uiBundles/reactbasic/package.json +6 -4
  73. package/lib/templates/uiBundles/reactbasic/patches/aria-hidden+1.2.6.patch +45 -0
  74. package/lib/templates/uiembedding/default/default.html +1 -1
  75. package/lib/utils/template-placeholders.d.ts +1 -1
  76. package/lib/utils/template-placeholders.js +3 -1
  77. package/lib/utils/template-placeholders.js.map +1 -1
  78. package/lib/utils/types.d.ts +1 -1
  79. package/package.json +5 -5
  80. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/README.md +0 -246
  81. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/language-switcher → _f_/_ls_}/__examples__/html-middleware-site-example.ts +0 -0
  82. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/language-switcher → _f_/_ls_}/__examples__/language-switcher-example.ts +0 -0
  83. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/language-switcher → _f_/_ls_}/build-language-url.ts +0 -0
  84. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/language-switcher → _f_/_ls_}/index.ts +0 -0
  85. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/language-switcher → _f_/_ls_}/language-switcher.html +0 -0
  86. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/language-switcher → _f_/_ls_}/language-switcher.ts +0 -0
  87. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/language-switcher → _f_/_ls_}/languages.ts +0 -0
  88. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/api/object-search.service.ts +0 -0
  89. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/breadcrumb/breadcrumb.html +0 -0
  90. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/breadcrumb/breadcrumb.ts +0 -0
  91. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/active-filters.html +0 -0
  92. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/active-filters.ts +0 -0
  93. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/date-filter.html +0 -0
  94. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/date-filter.ts +0 -0
  95. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/date-range-filter.html +0 -0
  96. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/date-range-filter.ts +0 -0
  97. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/filter-field-wrapper.html +0 -0
  98. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/filter-field-wrapper.ts +0 -0
  99. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/multi-select-filter.html +0 -0
  100. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/multi-select-filter.ts +0 -0
  101. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/numeric-range-filter.html +0 -0
  102. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/numeric-range-filter.ts +0 -0
  103. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/search-filter.html +0 -0
  104. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/search-filter.ts +0 -0
  105. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/select-filter.html +0 -0
  106. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/select-filter.ts +0 -0
  107. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/text-filter.html +0 -0
  108. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/text-filter.ts +0 -0
  109. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/object-search-state.service.ts +0 -0
  110. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/pagination/pagination.html +0 -0
  111. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/pagination/pagination.ts +0 -0
  112. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/search-bar/search-bar.html +0 -0
  113. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/search-bar/search-bar.ts +0 -0
  114. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/sort-control/sort-control.html +0 -0
  115. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/sort-control/sort-control.ts +0 -0
  116. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/utils/debounce.ts +0 -0
  117. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/utils/field-utils.ts +0 -0
  118. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/utils/filter-utils.ts +0 -0
  119. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/utils/index.ts +0 -0
  120. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/utils/sort-utils.ts +0 -0
  121. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/auth/auth.guard.ts +0 -0
  122. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/auth/auth.service.ts +0 -0
  123. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/auth-form/auth-form.html +0 -0
  124. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/auth-form/auth-form.ts +0 -0
  125. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/auth-layout/auth-layout.html +0 -0
  126. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/auth-layout/auth-layout.ts +0 -0
  127. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/card-layout/card-layout.html +0 -0
  128. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/card-layout/card-layout.ts +0 -0
  129. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/config/authentication.config.ts +0 -0
  130. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/footer-link/footer-link.html +0 -0
  131. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/footer-link/footer-link.ts +0 -0
  132. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/menu/auth-menu/auth-menu.html +0 -0
  133. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/menu/auth-menu/auth-menu.ts +0 -0
  134. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/reactive-form-page.base.ts +0 -0
  135. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/session/session.html +0 -0
  136. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/session/session.ts +0 -0
  137. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/status-alert/status-alert.html +0 -0
  138. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/status-alert/status-alert.ts +0 -0
  139. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/submit-button/submit-button.html +0 -0
  140. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/submit-button/submit-button.ts +0 -0
  141. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/utils/auth-helpers.ts +0 -0
  142. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/utils/auth-validators.ts +0 -0
  143. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/utils/helpers.ts +0 -0
  144. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/utils/session-time-servlet.ts +0 -0
  145. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/utils/session-timeout.config.ts +0 -0
  146. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/utils/session-timeout.service.ts +0 -0
  147. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/api/object-search.service.ts +0 -0
  148. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/breadcrumb/breadcrumb.html +0 -0
  149. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/breadcrumb/breadcrumb.ts +0 -0
  150. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/active-filters.html +0 -0
  151. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/active-filters.ts +0 -0
  152. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/date-filter.html +0 -0
  153. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/date-filter.ts +0 -0
  154. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/date-range-filter.html +0 -0
  155. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/date-range-filter.ts +0 -0
  156. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/filter-field-wrapper.html +0 -0
  157. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/filter-field-wrapper.ts +0 -0
  158. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/multi-select-filter.html +0 -0
  159. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/multi-select-filter.ts +0 -0
  160. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/numeric-range-filter.html +0 -0
  161. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/numeric-range-filter.ts +0 -0
  162. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/search-filter.html +0 -0
  163. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/search-filter.ts +0 -0
  164. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/select-filter.html +0 -0
  165. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/select-filter.ts +0 -0
  166. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/text-filter.html +0 -0
  167. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/text-filter.ts +0 -0
  168. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/object-search-state.service.ts +0 -0
  169. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/pagination/pagination.html +0 -0
  170. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/pagination/pagination.ts +0 -0
  171. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/search-bar/search-bar.html +0 -0
  172. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/search-bar/search-bar.ts +0 -0
  173. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/sort-control/sort-control.html +0 -0
  174. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/sort-control/sort-control.ts +0 -0
  175. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/utils/debounce.ts +0 -0
  176. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/utils/field-utils.ts +0 -0
  177. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/utils/filter-utils.ts +0 -0
  178. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/utils/index.ts +0 -0
  179. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/utils/sort-utils.ts +0 -0
  180. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/agentforce/agentforce-embed.service.ts +0 -0
  181. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/agentforce/conversation.html +0 -0
  182. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/agentforce/conversation.ts +0 -0
@@ -7,6 +7,7 @@
7
7
  "node": ">=22"
8
8
  },
9
9
  "scripts": {
10
+ "postinstall": "patch-package",
10
11
  "dev": "vite",
11
12
  "dev:design": "vite --mode design",
12
13
  "build": "tsc -b && vite build",
@@ -18,8 +19,8 @@
18
19
  "graphql:schema": "node scripts/get-graphql-schema.mjs"
19
20
  },
20
21
  "dependencies": {
21
- "@salesforce/platform-sdk": "^11.57.2",
22
- "@salesforce/ui-bundle": "^11.57.2",
22
+ "@salesforce/platform-sdk": "^12.4.5",
23
+ "@salesforce/ui-bundle": "^12.4.5",
23
24
  "@tailwindcss/vite": "^4.1.17",
24
25
  "class-variance-authority": "^0.7.1",
25
26
  "clsx": "^2.1.1",
@@ -35,7 +36,7 @@
35
36
  "tailwind-merge": "^3.5.0",
36
37
  "tailwindcss": "^4.1.17",
37
38
  "tw-animate-css": "^1.4.0",
38
- "@salesforce/agentforce-conversation-client": "^11.57.1"
39
+ "@salesforce/agentforce-conversation-client": "^12.0.0"
39
40
  },
40
41
  "devDependencies": {
41
42
  "@eslint/js": "^9.39.1",
@@ -45,8 +46,8 @@
45
46
  "@graphql-eslint/eslint-plugin": "^4.1.0",
46
47
  "@graphql-tools/utils": "^11.0.0",
47
48
  "@playwright/test": "^1.49.0",
48
- "@salesforce/graphiti": "^11.57.2",
49
- "@salesforce/vite-plugin-ui-bundle": "^11.57.2",
49
+ "@salesforce/graphiti": "^12.4.5",
50
+ "@salesforce/vite-plugin-ui-bundle": "^12.4.5",
50
51
  "@testing-library/jest-dom": "^6.6.3",
51
52
  "@testing-library/react": "^16.1.0",
52
53
  "@testing-library/user-event": "^14.5.2",
@@ -63,6 +64,7 @@
63
64
  "graphql": "^16.11.0",
64
65
  "graphql-codegen-typescript-operation-types": "^2.0.2",
65
66
  "jsdom": "^25.0.1",
67
+ "patch-package": "^8.0.1",
66
68
  "serve": "^14.2.5",
67
69
  "typescript": "~5.9.3",
68
70
  "typescript-eslint": "^8.46.4",
@@ -0,0 +1,45 @@
1
+ diff --git a/node_modules/aria-hidden/dist/es2015/index.js b/node_modules/aria-hidden/dist/es2015/index.js
2
+ index 00aa2ea..1d2f61e 100644
3
+ --- a/node_modules/aria-hidden/dist/es2015/index.js
4
+ +++ b/node_modules/aria-hidden/dist/es2015/index.js
5
+ @@ -131,7 +131,9 @@ export var hideOthers = function (originalTarget, parentNode, markerName) {
6
+ // we should not hide aria-live elements - https://github.com/theKashey/aria-hidden/issues/10
7
+ // and script elements, as they have no impact on accessibility.
8
+ targets.push.apply(targets, Array.from(activeParentNode.querySelectorAll('[aria-live], script')));
9
+ - return applyAttributeToOthers(targets, activeParentNode, markerName, 'aria-hidden');
10
+ + return supportsInert()
11
+ + ? inertOthers(targets, activeParentNode, markerName)
12
+ + : applyAttributeToOthers(targets, activeParentNode, markerName, 'aria-hidden');
13
+ };
14
+ /**
15
+ * Marks everything except given node(or nodes) as inert
16
+ diff --git a/node_modules/aria-hidden/dist/es2019/index.js b/node_modules/aria-hidden/dist/es2019/index.js
17
+ index 17404da..a444872 100644
18
+ --- a/node_modules/aria-hidden/dist/es2019/index.js
19
+ +++ b/node_modules/aria-hidden/dist/es2019/index.js
20
+ @@ -126,7 +126,9 @@ export const hideOthers = (originalTarget, parentNode, markerName = 'data-aria-h
21
+ // we should not hide aria-live elements - https://github.com/theKashey/aria-hidden/issues/10
22
+ // and script elements, as they have no impact on accessibility.
23
+ targets.push(...Array.from(activeParentNode.querySelectorAll('[aria-live], script')));
24
+ - return applyAttributeToOthers(targets, activeParentNode, markerName, 'aria-hidden');
25
+ + return supportsInert()
26
+ + ? inertOthers(targets, activeParentNode, markerName)
27
+ + : applyAttributeToOthers(targets, activeParentNode, markerName, 'aria-hidden');
28
+ };
29
+ /**
30
+ * Marks everything except given node(or nodes) as inert
31
+ diff --git a/node_modules/aria-hidden/dist/es5/index.js b/node_modules/aria-hidden/dist/es5/index.js
32
+ index 1b86f56..863f83e 100644
33
+ --- a/node_modules/aria-hidden/dist/es5/index.js
34
+ +++ b/node_modules/aria-hidden/dist/es5/index.js
35
+ @@ -134,7 +134,9 @@ var hideOthers = function (originalTarget, parentNode, markerName) {
36
+ // we should not hide aria-live elements - https://github.com/theKashey/aria-hidden/issues/10
37
+ // and script elements, as they have no impact on accessibility.
38
+ targets.push.apply(targets, Array.from(activeParentNode.querySelectorAll('[aria-live], script')));
39
+ - return applyAttributeToOthers(targets, activeParentNode, markerName, 'aria-hidden');
40
+ + return supportsInert()
41
+ + ? inertOthers(targets, activeParentNode, markerName)
42
+ + : applyAttributeToOthers(targets, activeParentNode, markerName, 'aria-hidden');
43
+ };
44
+ exports.hideOthers = hideOthers;
45
+ /**
@@ -60,13 +60,16 @@ function isSearchable(entry: RawContentTypeEntry): boolean {
60
60
  return true;
61
61
  }
62
62
 
63
- /**
64
- * Extracts the prefixed FQN from an entry. `id` is the current field; the
65
- * others are fallbacks for alternate/legacy payloads. `name` is intentionally
66
- * NOT read here — it is the bare (unprefixed) developer name and would fail
67
- * `isValidCmsFqn`.
68
- */
63
+ /** ManagedContentType key prefix — the `id` custom content types carry instead of an FQN. */
64
+ const CONTENT_TYPE_RECORD_ID_PATTERN = /^0T1[a-zA-Z0-9]{12,15}$/;
65
+
66
+ /** Extracts the prefixed FQN from an entry, rebuilding `c__<name>` when `id` is a custom-type record id. */
69
67
  function entryFqn(entry: RawContentTypeEntry): string | undefined {
68
+ // Custom types put a ManagedContentType id in `id`; the FQN the GraphQL filter
69
+ // accepts is `c__<name>`.
70
+ if (entry.id && CONTENT_TYPE_RECORD_ID_PATTERN.test(entry.id)) {
71
+ return entry.name ? `c__${entry.name}` : undefined;
72
+ }
70
73
  return entry.id ?? entry.contentType ?? entry.fqn ?? entry.developerName;
71
74
  }
72
75
 
@@ -5,7 +5,8 @@
5
5
  *
6
6
  * The `$cmsContentTypeFQNs` declaration, its `contentTypeFQNs:` argument, and
7
7
  * its variable value are all emitted only when a non-empty content-type list is
8
- * passed; on the bootstrap call (no types known) they are omitted entirely.
8
+ * passed; on the bootstrap call (no types known) they are omitted entirely. The
9
+ * `$cmsLanguage` pieces follow the same conditional pattern.
9
10
  */
10
11
 
11
12
  import type { QueryFragmentContribution } from "../types";
@@ -26,6 +27,8 @@ export interface BuildCmsFragmentParams {
26
27
  * `isValidCmsFqn`-validated values.
27
28
  */
28
29
  contentTypes?: string[] | null;
30
+ /** User language tag for the `language` argument; emitted only when a non-empty string. */
31
+ language?: string | null;
29
32
  }
30
33
 
31
34
  /**
@@ -33,9 +36,10 @@ export interface BuildCmsFragmentParams {
33
36
  * included only when `params.contentTypes` is a non-empty array.
34
37
  */
35
38
  export function buildCmsQueryFragment(params: BuildCmsFragmentParams): QueryFragmentContribution {
36
- const { keyword, uiBundleId, offset, limit, contentTypes } = params;
39
+ const { keyword, uiBundleId, offset, limit, contentTypes, language } = params;
37
40
 
38
41
  const hasContentTypes = Array.isArray(contentTypes) && contentTypes.length > 0;
42
+ const hasLanguage = typeof language === "string" && language.length > 0;
39
43
 
40
44
  const variableDeclarations = [
41
45
  "$cmsKeyword: String!",
@@ -61,12 +65,19 @@ export function buildCmsQueryFragment(params: BuildCmsFragmentParams): QueryFrag
61
65
  variables.cmsContentTypeFQNs = contentTypes;
62
66
  }
63
67
 
68
+ // Top-level `language: String` arg, emitted only when a non-empty tag is provided.
69
+ const languageArg = hasLanguage ? "\n language: $cmsLanguage" : "";
70
+ if (hasLanguage) {
71
+ variableDeclarations.push("$cmsLanguage: String");
72
+ variables.cmsLanguage = language;
73
+ }
74
+
64
75
  const fragment = `managed_content {
65
76
  search {
66
77
  searchContentInChannels(
67
78
  searchIdentifiers: { uibundleIds: [$UIBundleId] }
68
79
  keyword: $cmsKeyword
69
- pagination: { limit: $cmsLimit, offset: $cmsOffset }${contentTypesArg}
80
+ pagination: { limit: $cmsLimit, offset: $cmsOffset }${languageArg}${contentTypesArg}
70
81
  ) {
71
82
  total
72
83
  offset
@@ -8,21 +8,23 @@
8
8
 
9
9
  /**
10
10
  * Formats a CMS content type FQN into a human-readable label.
11
- * Strips "sfdc_cms__" prefix, splits camelCase, title-cases each word.
11
+ * Strips the "sfdc_cms__" (OOTB) or "c__" (custom) prefix, splits camelCase, title-cases each word.
12
12
  *
13
13
  * Examples:
14
14
  * "sfdc_cms__blogPost" → "Blog Post"
15
15
  * "sfdc_cms__news" → "News"
16
- * "sfdc_cms__document" → "Document"
17
- * "sfdc_cms__pressRelease" → "Press Release"
16
+ * "c__NewsArticle" → "News Article"
17
+ * "c__Recipe" → "Recipe"
18
18
  */
19
19
  export function formatContentTypeLabel(fqn: string): string {
20
- const stripped = fqn.replace(/^sfdc_cms__/, "");
20
+ const stripped = fqn.replace(/^(?:sfdc_cms__|c__)/, "");
21
21
  const words = stripped.replace(/([a-z])([A-Z])/g, "$1 $2");
22
22
  return words.charAt(0).toUpperCase() + words.slice(1);
23
23
  }
24
24
 
25
- export const CMS_FQN_PATTERN = /^sfdc_cms__[a-zA-Z][a-zA-Z0-9]{0,39}$/;
25
+ // OOTB content types are namespaced `sfdc_cms__<name>`; custom types created in
26
+ // a namespace-less org take the default `c__<name>` prefix. Both are valid FQNs.
27
+ export const CMS_FQN_PATTERN = /^(?:sfdc_cms__|c__)[a-zA-Z][a-zA-Z0-9]{0,39}$/;
26
28
 
27
29
  export function isValidCmsFqn(fqn: string): boolean {
28
30
  return CMS_FQN_PATTERN.test(fqn);
@@ -44,6 +44,7 @@ function buildCmsRequest(request: SourceRequest<CmsSourceConfig>): QueryFragment
44
44
  offset,
45
45
  limit: request.pageSize,
46
46
  contentTypes: contentTypes && contentTypes.length > 0 ? contentTypes : null,
47
+ language: request.language ?? null,
47
48
  });
48
49
  }
49
50
 
@@ -1,4 +1,5 @@
1
- import { getCurrentApp } from "@salesforce/platform-sdk";
1
+ import { createDataSDK, getCurrentApp } from "@salesforce/platform-sdk";
2
+ import { fetchI18nContext } from "@salesforce/platform-sdk/i18n";
2
3
 
3
4
  /** Returns the UIBundle id for CMS search, resolved from the running app. */
4
5
  export async function getUIBundleId(): Promise<string> {
@@ -6,6 +7,21 @@ export async function getUIBundleId(): Promise<string> {
6
7
  return app.identity?.bundleId ?? "";
7
8
  }
8
9
 
10
+ /** Fallback language tag used when the user's own language can't be resolved. */
11
+ export const DEFAULT_LANGUAGE = "en-US";
12
+
13
+ /** Returns the current user's language tag (e.g. `"en-US"`), falling back to {@link DEFAULT_LANGUAGE}. */
14
+ export async function getUserLanguage(): Promise<string> {
15
+ try {
16
+ const sdk = await createDataSDK();
17
+ if (!sdk?.graphql) return DEFAULT_LANGUAGE;
18
+ const ctx = await fetchI18nContext(sdk);
19
+ return ctx.lang || DEFAULT_LANGUAGE;
20
+ } catch {
21
+ return DEFAULT_LANGUAGE;
22
+ }
23
+ }
24
+
9
25
  /** Shape guard for a UIBundle record id (9YE + 12-15 alphanumerics). */
10
26
  export const UI_BUNDLE_ID_PATTERN = /^9YE[a-zA-Z0-9]{12,15}$/;
11
27
 
@@ -81,6 +81,8 @@ export interface SourceRequest<T extends SourceConfig = SourceConfig> {
81
81
  * synchronous. Ignored by non-CMS adapters.
82
82
  */
83
83
  uiBundleId?: string;
84
+ /** CMS-only, optional. The current user's language tag fed to the CMS search `language` argument. */
85
+ language?: string;
84
86
  }
85
87
 
86
88
  /**
@@ -40,6 +40,7 @@ import { FilterProvider, FilterResetButton } from "./filters/FilterContext";
40
40
  import { PaginationControls } from "./controls/PaginationControls";
41
41
  import { SortControl } from "./controls/SortControl";
42
42
  import { resolveResultRenderer, type ResultRenderer } from "./results/resolveResultRenderer";
43
+ import { MIN_QUERY_LENGTH } from "../constants";
43
44
  import { ALL_SCOPE, type SearchHandle, type SourceController } from "../types";
44
45
 
45
46
  export interface MergedSearchResultsProps {
@@ -52,6 +53,8 @@ export interface MergedSearchResultsProps {
52
53
  renderResult?: Record<string, ResultRenderer>;
53
54
  /** Message shown when there are no results. Defaults to "No results found." */
54
55
  emptyMessage?: string;
56
+ /** Message shown when the query is below {@link MIN_QUERY_LENGTH}. Defaults to "Start your search…" */
57
+ shortQueryMessage?: string;
55
58
  /** Number of skeleton placeholders to show while loading. Defaults to `pageSize`. */
56
59
  skeletonCount?: number;
57
60
  /** Extra classes on the grid container. Defaults to a responsive 1/2/3-column grid. */
@@ -81,11 +84,16 @@ export function MergedSearchResults({
81
84
  handle,
82
85
  renderResult,
83
86
  emptyMessage,
87
+ shortQueryMessage = "Start your search…",
84
88
  skeletonCount,
85
89
  gridClassName = DEFAULT_GRID_CLASS,
86
90
  className,
87
91
  }: MergedSearchResultsProps) {
88
- const { globalPagination: page, mergedResults, loading, error } = handle;
92
+ const { globalPagination: page, mergedResults, loading, error, q } = handle;
93
+ // Distinguishes "haven't typed enough yet" (useSearch short-circuits below
94
+ // MIN_QUERY_LENGTH without fetching) from a genuine zero-match search — both
95
+ // otherwise present identically as loading=false, mergedResults=[].
96
+ const isShortQuery = q.trim().length < MIN_QUERY_LENGTH;
89
97
 
90
98
  // A single source is in scope when the scope is narrowed (selected from the
91
99
  // dropdown) or locked (`lockedScope` / restrictTo). Its filter chrome can
@@ -111,9 +119,11 @@ export function MergedSearchResults({
111
119
  ? "Searching…"
112
120
  : error
113
121
  ? "Search failed."
114
- : mergedResults.length === 0
115
- ? (emptyMessage ?? "No results found.")
116
- : `${page.totalCount} result${page.totalCount === 1 ? "" : "s"}`;
122
+ : isShortQuery
123
+ ? shortQueryMessage
124
+ : mergedResults.length === 0
125
+ ? (emptyMessage ?? "No results found.")
126
+ : `${page.totalCount} result${page.totalCount === 1 ? "" : "s"}`;
117
127
 
118
128
  return (
119
129
  <div className={`space-y-6 ${className ?? ""}`}>
@@ -161,6 +171,8 @@ export function MergedSearchResults({
161
171
  <Skeleton key={i} className="h-48 w-full rounded-lg" />
162
172
  ))}
163
173
  </div>
174
+ ) : isShortQuery ? (
175
+ <p className="py-12 text-center text-sm text-muted-foreground">{shortQueryMessage}</p>
164
176
  ) : mergedResults.length === 0 ? (
165
177
  <p className="py-12 text-center text-sm text-muted-foreground">
166
178
  {emptyMessage ?? "No results found."}
@@ -80,6 +80,7 @@ export function SearchResults({
80
80
  renderFilters={resolvedRenderFilters}
81
81
  hideFilterChrome={filtersHidden}
82
82
  emptyMessage={emptyMessages?.[key]}
83
+ q={handle.q}
83
84
  />
84
85
  );
85
86
  })}
@@ -9,13 +9,14 @@
9
9
  */
10
10
 
11
11
  import type { ReactNode } from "react";
12
- import { AlertCircle, SearchX } from "lucide-react";
12
+ import { AlertCircle, Search, SearchX } from "lucide-react";
13
13
  import { Alert, AlertDescription, AlertTitle } from "../../../components/ui/alert";
14
14
  import { Skeleton } from "../../../components/ui/skeleton";
15
15
  import { ActiveFilters } from "./filters/ActiveFilters";
16
16
  import { FilterProvider, FilterResetButton } from "./filters/FilterContext";
17
17
  import { PaginationControls } from "./controls/PaginationControls";
18
18
  import { SortControl } from "./controls/SortControl";
19
+ import { MIN_QUERY_LENGTH } from "../constants";
19
20
  import type { SourceController } from "../types";
20
21
 
21
22
  interface SourceSectionProps {
@@ -35,6 +36,15 @@ interface SourceSectionProps {
35
36
  */
36
37
  hideFilterChrome?: boolean;
37
38
  emptyMessage?: string;
39
+ /**
40
+ * The current search query. Used to distinguish a not-yet-started search
41
+ * (query below {@link MIN_QUERY_LENGTH}, so no fetch was issued) from a
42
+ * genuine zero-match result, so the empty state can prompt instead of
43
+ * claiming "No … found."
44
+ */
45
+ q?: string;
46
+ /** Message shown when the query is below {@link MIN_QUERY_LENGTH}. Defaults to "Start your search…" */
47
+ shortQueryMessage?: string;
38
48
  className?: string;
39
49
  }
40
50
 
@@ -44,6 +54,8 @@ export function SourceSection({
44
54
  renderFilters,
45
55
  hideFilterChrome,
46
56
  emptyMessage,
57
+ q = "",
58
+ shortQueryMessage = "Start your search…",
47
59
  className,
48
60
  }: SourceSectionProps) {
49
61
  const { config, result, loading, error, filters, sort, pagination } = controller;
@@ -51,6 +63,9 @@ export function SourceSection({
51
63
  const totalCount = result?.totalCount;
52
64
  const showResults = !loading && !error && nodes.length > 0;
53
65
  const showEmpty = !loading && !error && nodes.length === 0;
66
+ // A query below MIN_QUERY_LENGTH never triggers a fetch, so an empty result
67
+ // means "not searched yet" — prompt rather than report zero matches.
68
+ const isShortQuery = q.trim().length < MIN_QUERY_LENGTH;
54
69
 
55
70
  const pageSizeOptions = pagination.pageSizeOptions ?? [pagination.pageSize];
56
71
 
@@ -70,7 +85,9 @@ export function SourceSection({
70
85
  : error
71
86
  ? `Failed to load ${config.label}.`
72
87
  : showEmpty
73
- ? (emptyMessage ?? `No ${config.label.toLowerCase()} found.`)
88
+ ? isShortQuery
89
+ ? shortQueryMessage
90
+ : (emptyMessage ?? `No ${config.label.toLowerCase()} found.`)
74
91
  : totalCount != null
75
92
  ? `${config.label}: ${totalCount} ${totalCount === 1 ? "result" : "results"}`
76
93
  : "";
@@ -153,9 +170,15 @@ export function SourceSection({
153
170
 
154
171
  {showEmpty && (
155
172
  <div className="flex flex-col items-center justify-center py-10 text-center">
156
- <SearchX className="size-10 text-muted-foreground mb-3" />
173
+ {isShortQuery ? (
174
+ <Search className="size-10 text-muted-foreground mb-3" />
175
+ ) : (
176
+ <SearchX className="size-10 text-muted-foreground mb-3" />
177
+ )}
157
178
  <p className="text-sm text-muted-foreground">
158
- {emptyMessage ?? `No ${config.label.toLowerCase()} found.`}
179
+ {isShortQuery
180
+ ? shortQueryMessage
181
+ : (emptyMessage ?? `No ${config.label.toLowerCase()} found.`)}
159
182
  </p>
160
183
  </div>
161
184
  )}
@@ -64,7 +64,13 @@ function DefaultFilterField({ filter, source }: DefaultFilterFieldProps) {
64
64
  return <PicklistField filter={filter} source={source} multi={true} />;
65
65
  case "numeric":
66
66
  return (
67
- <NumericRangeFilter field={filter.field} label={filter.label} helpText={filter.helpText} />
67
+ <NumericRangeFilter
68
+ field={filter.field}
69
+ label={filter.label}
70
+ helpText={filter.helpText}
71
+ min={filter.min}
72
+ max={filter.max}
73
+ />
68
74
  );
69
75
  case "boolean":
70
76
  return <BooleanFilter field={filter.field} label={filter.label} helpText={filter.helpText} />;
@@ -40,7 +40,11 @@ import { MIN_QUERY_LENGTH } from "../constants";
40
40
  // `resolveChannelId` derives the discovery channel id from a CMS result;
41
41
  // `useSearchableContentTypes` is the session-cached discovery hook;
42
42
  // `isValidCmsFqn` validates a selected FQN before it enters state or a query.
43
- import { getUIBundleId, isConfiguredUIBundleId } from "../adapters/cms/searchChannel";
43
+ import {
44
+ getUIBundleId,
45
+ getUserLanguage,
46
+ isConfiguredUIBundleId,
47
+ } from "../adapters/cms/searchChannel";
44
48
  import { getOrgSupportsCmsSearch } from "../adapters/cms/api/orgApiVersionService";
45
49
  import { resolveChannelId } from "../adapters/cms/channelResolver";
46
50
  import { useSearchableContentTypes } from "../adapters/cms/hooks/useSearchableContentTypes";
@@ -98,7 +102,7 @@ interface ResolvedPagination {
98
102
  }
99
103
 
100
104
  /** Resolves the single global pagination config, filling in defaults. */
101
- function resolvePagination(config: SearchConfig): ResolvedPagination {
105
+ export function resolvePagination(config: SearchConfig): ResolvedPagination {
102
106
  const p = config.pagination;
103
107
  const pageSizeOptions =
104
108
  p?.pageSizeOptions && p.pageSizeOptions.length > 0
@@ -119,7 +123,7 @@ function validatePageSize(pagination: ResolvedPagination, size: number): number
119
123
  return valid.includes(size) ? size : pagination.pageSize;
120
124
  }
121
125
 
122
- function initSourceState(
126
+ export function initSourceState(
123
127
  source: SourceConfig,
124
128
  params: URLSearchParams,
125
129
  pagination: ResolvedPagination,
@@ -135,7 +139,8 @@ function initSourceState(
135
139
  filters: read.filters,
136
140
  sort,
137
141
  pageSize,
138
- pageIndex: read.pageIndex,
142
+ // Cursors aren't persisted to the URL, so always seed the first page (page param ignored on read).
143
+ pageIndex: 0,
139
144
  afterCursor: undefined,
140
145
  cursorStack: [],
141
146
  };
@@ -503,8 +508,8 @@ export function useSearch(config: SearchConfig, options?: UseSearchOptions): Sea
503
508
  // page-level error — content-type narrowing is an enhancement, not a
504
509
  // hard requirement. Read in `.catch` below.
505
510
  let isDiscoveryRefetch = false;
506
- Promise.all([getUIBundleId(), getOrgSupportsCmsSearch()])
507
- .then(([uiBundleId, orgSupportsCmsSearch]) => {
511
+ Promise.all([getUIBundleId(), getUserLanguage(), getOrgSupportsCmsSearch()])
512
+ .then(([uiBundleId, userLanguage, orgSupportsCmsSearch]) => {
508
513
  if (cancelled || generation !== fetchGenRef.current) return;
509
514
 
510
515
  const requests: SourceRequest[] = activeSources.flatMap((source) => {
@@ -541,7 +546,7 @@ export function useSearch(config: SearchConfig, options?: UseSearchOptions): Sea
541
546
  if (!cmsFqn && discoveredContentTypeFqns.length > 0) {
542
547
  isDiscoveryRefetch = true;
543
548
  }
544
- return [{ ...base, uiBundleId, contentTypes }];
549
+ return [{ ...base, uiBundleId, language: userLanguage, contentTypes }];
545
550
  });
546
551
 
547
552
  // Every in-scope source was gated out (e.g. a CMS-only scope while CMS
@@ -49,6 +49,9 @@ export type FilterFieldConfig<TFieldName extends string = string> = {
49
49
  /** Date-family only — see {@link DateFilterMode}. Defaults to `"comparison"`. */
50
50
  dateMode?: DateFilterMode;
51
51
  helpText?: string;
52
+ // Numeric-family only.
53
+ min?: number;
54
+ max?: number;
52
55
  };
53
56
 
54
57
  export type ActiveFilterValue<TFieldName extends string = string> = {
@@ -205,12 +208,17 @@ function toEndOfDay(d: string) {
205
208
  return `${d}T23:59:59.999Z`;
206
209
  }
207
210
 
208
- function buildSingleFilter(filter: ActiveFilterValue, _config?: FilterFieldConfig): unknown {
211
+ /** Escapes LIKE metacharacters (`\`, `%`, `_`) so user input is matched literally. */
212
+ function escapeLikePattern(value: string): string {
213
+ return value.replace(/[\\%_]/g, "\\$&");
214
+ }
215
+
216
+ function buildSingleFilter(filter: ActiveFilterValue, config?: FilterFieldConfig): unknown {
209
217
  const { field, type, value, min, max } = filter;
210
218
  switch (type) {
211
219
  case "text": {
212
220
  if (!value) return null;
213
- return { [field]: { like: `%${value}%` } };
221
+ return { [field]: { like: `%${escapeLikePattern(value)}%` } };
214
222
  }
215
223
  case "picklist": {
216
224
  if (!value) return null;
@@ -219,8 +227,19 @@ function buildSingleFilter(filter: ActiveFilterValue, _config?: FilterFieldConfi
219
227
  case "numeric": {
220
228
  if (!min && !max) return null;
221
229
  const ops: Record<string, number> = {};
222
- if (min) ops.gte = Number(min);
223
- if (max) ops.lte = Number(max);
230
+ // clamp both endpoints into the declared [min,max] bounds
231
+ const clamp = (n: number) => {
232
+ if (config?.min != null) n = Math.max(n, config.min);
233
+ if (config?.max != null) n = Math.min(n, config.max);
234
+ return n;
235
+ };
236
+ // Drop any endpoint that isn't finite — NaN/Infinity serialize to null on the wire.
237
+ const lo = Number(min);
238
+ if (min && Number.isFinite(lo)) ops.gte = clamp(lo);
239
+ const hi = Number(max);
240
+ if (max && Number.isFinite(hi)) ops.lte = clamp(hi);
241
+ // If both endpoints were dropped as invalid, emit no clause at all.
242
+ if (Object.keys(ops).length === 0) return null;
224
243
  return { [field]: ops };
225
244
  }
226
245
  case "boolean": {
@@ -275,7 +294,7 @@ export function buildGlobalQueryClause(q: string, searchableFields: string[]): u
275
294
  if (!trimmed || searchableFields.length === 0) return undefined;
276
295
  const clauses = searchableFields.map((path) => {
277
296
  const parts = path.split(".");
278
- let clause: Record<string, unknown> = { like: `%${trimmed}%` };
297
+ let clause: Record<string, unknown> = { like: `%${escapeLikePattern(trimmed)}%` };
279
298
  for (let i = parts.length - 1; i >= 0; i--) {
280
299
  clause = { [parts[i]]: clause };
281
300
  }
@@ -1,11 +1,26 @@
1
1
  import { AgentforceConversationClient } from "./components/AgentforceConversationClient";
2
2
  import { Outlet, Link, useLocation } from "react-router";
3
3
  import { getAllRoutes } from "./router-utils";
4
- import { useState } from "react";
4
+ import { useEffect, useId, useRef, useState } from "react";
5
5
 
6
6
  export default function AppLayout() {
7
7
  const [isOpen, setIsOpen] = useState(false);
8
8
  const location = useLocation();
9
+ const navId = useId();
10
+ const outletWrapperRef = useRef<HTMLDivElement>(null);
11
+ const previousPathname = useRef<string | null>(null);
12
+
13
+ useEffect(() => {
14
+ if (previousPathname.current !== null && previousPathname.current !== location.pathname) {
15
+ const wrapper = outletWrapperRef.current;
16
+ const target = wrapper?.querySelector<HTMLElement>("h1, main, [role='main']") ?? wrapper;
17
+ if (target) {
18
+ if (!target.hasAttribute("tabindex")) target.setAttribute("tabindex", "-1");
19
+ target.focus();
20
+ }
21
+ }
22
+ previousPathname.current = location.pathname;
23
+ }, [location.pathname]);
9
24
 
10
25
  const isActive = (path: string) => location.pathname === path;
11
26
 
@@ -28,6 +43,20 @@ export default function AppLayout() {
28
43
 
29
44
  return (
30
45
  <>
46
+ <a
47
+ href="#main-content"
48
+ onClick={(e) => {
49
+ e.preventDefault();
50
+ const content = outletWrapperRef.current;
51
+ if (content) {
52
+ if (!content.hasAttribute("tabindex")) content.setAttribute("tabindex", "-1");
53
+ content.focus();
54
+ }
55
+ }}
56
+ className="sr-only focus:not-sr-only focus:absolute focus:z-50 focus:top-2 focus:left-2 focus:rounded-md focus:bg-white focus:px-4 focus:py-2 focus:text-gray-900 focus:shadow focus:ring-2 focus:ring-gray-900"
57
+ >
58
+ Skip to main content
59
+ </a>
31
60
  <nav className="bg-white border-b border-gray-200">
32
61
  <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
33
62
  <div className="flex justify-between items-center h-16">
@@ -38,8 +67,10 @@ export default function AppLayout() {
38
67
  onClick={toggleMenu}
39
68
  className="p-2 rounded-md text-gray-700 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
40
69
  aria-label="Toggle menu"
70
+ aria-expanded={isOpen}
71
+ aria-controls={navId}
41
72
  >
42
- <div className="w-6 h-6 flex flex-col justify-center space-y-1.5">
73
+ <div aria-hidden="true" className="w-6 h-6 flex flex-col justify-center space-y-1.5">
43
74
  <span
44
75
  className={`block h-0.5 w-6 bg-current transition-all ${
45
76
  isOpen ? "rotate-45 translate-y-2" : ""
@@ -56,14 +87,15 @@ export default function AppLayout() {
56
87
  </div>
57
88
  </button>
58
89
  </div>
59
- {isOpen && (
60
- <div className="pb-4">
90
+ <div id={navId} className="pb-4" hidden={!isOpen}>
91
+ {isOpen && (
61
92
  <div className="flex flex-col space-y-2">
62
93
  {navigationRoutes.map((item) => (
63
94
  <Link
64
95
  key={item.path}
65
96
  to={item.path}
66
97
  onClick={() => setIsOpen(false)}
98
+ aria-current={isActive(item.path) ? "page" : undefined}
67
99
  className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${
68
100
  isActive(item.path)
69
101
  ? "bg-blue-100 text-blue-700"
@@ -74,11 +106,13 @@ export default function AppLayout() {
74
106
  </Link>
75
107
  ))}
76
108
  </div>
77
- </div>
78
- )}
109
+ )}
110
+ </div>
79
111
  </div>
80
112
  </nav>
81
- <Outlet />
113
+ <div id="main-content" ref={outletWrapperRef}>
114
+ <Outlet />
115
+ </div>
82
116
  <AgentforceConversationClient agentId="<USER_AGENT_ID_18_CHAR_0Xx...>" />
83
117
  </>
84
118
  );
@@ -9,11 +9,7 @@ import {
9
9
  } from "./ui/breadcrumb";
10
10
  import { Skeleton } from "./ui/skeleton";
11
11
 
12
- /**
13
- * Home → List → Record breadcrumb for object pages. Previously imported from
14
- * feature-react-object-search; inlined here so the app no longer depends on
15
- * that feature.
16
- */
12
+ /** Home → List → Record breadcrumb, shared so every object page renders the same navigation trail. */
17
13
  interface ObjectBreadcrumbProps {
18
14
  listPath: string;
19
15
  listLabel: string;