@salesforce/templates 66.14.0 → 66.16.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 (200) hide show
  1. package/lib/generators/lightningOutGenerator.d.ts +7 -0
  2. package/lib/generators/lightningOutGenerator.js +122 -0
  3. package/lib/generators/lightningOutGenerator.js.map +1 -0
  4. package/lib/generators/uiEmbeddingGenerator.js +6 -6
  5. package/lib/generators/uiEmbeddingGenerator.js.map +1 -1
  6. package/lib/i18n/i18n.d.ts +12 -2
  7. package/lib/i18n/i18n.js +12 -2
  8. package/lib/i18n/i18n.js.map +1 -1
  9. package/lib/index.d.ts +1 -0
  10. package/lib/index.js +1 -0
  11. package/lib/index.js.map +1 -1
  12. package/lib/templates/lightningout/default/corsWhitelistOrigin.xml +4 -0
  13. package/lib/templates/lightningout/default/externalClientApplication.xml +7 -0
  14. package/lib/templates/lightningout/default/extlClntAppGlobalOauthSettings.xml +7 -0
  15. package/lib/templates/lightningout/default/extlClntAppOauthSettings.xml +6 -0
  16. package/lib/templates/lightningout/default/lightningOutApp.xml +20 -0
  17. package/lib/templates/lightningout/default/myDomainSettings.xml +4 -0
  18. package/lib/templates/lightningout/default/securitySettings.xml +6 -0
  19. package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/package-lock.json +608 -506
  20. package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/package-lock.json +611 -509
  21. package/lib/templates/project/reactexternalapp/CHANGELOG.md +437 -0
  22. package/lib/templates/project/reactexternalapp/README.md +2 -0
  23. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/package-lock.json +694 -447
  24. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/package.json +6 -4
  25. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/patches/aria-hidden+1.2.6.patch +45 -0
  26. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/pages/Login.tsx +3 -2
  27. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/pages/Register.tsx +3 -2
  28. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/api/searchableContentTypesService.ts +9 -6
  29. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/cmsQueryFragment.ts +14 -3
  30. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/contentTypeUtils.ts +7 -5
  31. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/index.ts +1 -0
  32. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/searchChannel.ts +17 -1
  33. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/types.ts +2 -0
  34. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/MergedSearchResults.tsx +16 -4
  35. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SearchResults.tsx +1 -0
  36. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SourceSection.tsx +27 -4
  37. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/hooks/useSearch.ts +12 -7
  38. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/filterUtils.ts +14 -4
  39. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/appLayout.tsx +42 -7
  40. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/ObjectBreadcrumb.tsx +1 -5
  41. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/AccountObjectDetailPage.tsx +34 -47
  42. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/Home.tsx +3 -2
  43. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/utils/accountFields.ts +1 -14
  44. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/tsconfig.tsbuildinfo +1 -1
  45. package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleAuthUtils_Test.cls +141 -0
  46. package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleAuthUtils_Test.cls-meta.xml +5 -0
  47. package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleSocialLoginConfig.cls +23 -11
  48. package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleSocialLoginConfig_Test.cls +80 -0
  49. package/lib/templates/project/reactexternalapp/package.json +1 -1
  50. package/lib/templates/project/reactexternalapp/scripts/org-setup-config-schema.mjs +14 -2
  51. package/lib/templates/project/reactexternalapp/scripts/org-setup-dev.mjs +27 -0
  52. package/lib/templates/project/reactexternalapp/scripts/org-setup-url.mjs +108 -0
  53. package/lib/templates/project/reactexternalapp/scripts/org-setup-xml.mjs +96 -0
  54. package/lib/templates/project/reactexternalapp/scripts/org-setup.mjs +195 -3
  55. package/lib/templates/project/reactinternalapp/CHANGELOG.md +533 -0
  56. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/package-lock.json +695 -448
  57. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/package.json +7 -5
  58. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/patches/aria-hidden+1.2.6.patch +45 -0
  59. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/api/searchableContentTypesService.ts +9 -6
  60. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/cmsQueryFragment.ts +14 -3
  61. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/contentTypeUtils.ts +7 -5
  62. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/index.ts +1 -0
  63. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/cms/searchChannel.ts +17 -1
  64. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/adapters/types.ts +2 -0
  65. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/MergedSearchResults.tsx +16 -4
  66. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SearchResults.tsx +1 -0
  67. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SourceSection.tsx +27 -4
  68. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/DefaultFilterPanel.tsx +7 -1
  69. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/hooks/useSearch.ts +12 -7
  70. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/filterUtils.ts +24 -5
  71. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/appLayout.tsx +41 -7
  72. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/ObjectBreadcrumb.tsx +1 -5
  73. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/pages/AccountObjectDetailPage.tsx +40 -50
  74. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/pages/Home.tsx +7 -2
  75. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/utils/accountFields.ts +1 -14
  76. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/tsconfig.tsbuildinfo +1 -1
  77. package/lib/templates/project/reactinternalapp/package.json +1 -1
  78. package/lib/templates/project/reactinternalapp/scripts/org-setup-config-schema.mjs +14 -2
  79. package/lib/templates/project/reactinternalapp/scripts/org-setup-dev.mjs +27 -0
  80. package/lib/templates/project/reactinternalapp/scripts/org-setup-url.mjs +108 -0
  81. package/lib/templates/project/reactinternalapp/scripts/org-setup-xml.mjs +96 -0
  82. package/lib/templates/project/reactinternalapp/scripts/org-setup.mjs +195 -3
  83. package/lib/templates/uiBundles/angularbasic/package-lock.json +608 -506
  84. package/lib/templates/uiBundles/reactbasic/package-lock.json +691 -444
  85. package/lib/templates/uiBundles/reactbasic/package.json +6 -4
  86. package/lib/templates/uiBundles/reactbasic/patches/aria-hidden+1.2.6.patch +45 -0
  87. package/lib/templates/uiembedding/default/default.html +1 -1
  88. package/lib/utils/lightningOut.d.ts +42 -0
  89. package/lib/utils/lightningOut.js +119 -0
  90. package/lib/utils/lightningOut.js.map +1 -0
  91. package/lib/utils/template-placeholders.d.ts +1 -1
  92. package/lib/utils/template-placeholders.js +3 -1
  93. package/lib/utils/template-placeholders.js.map +1 -1
  94. package/lib/utils/types.d.ts +29 -7
  95. package/lib/utils/types.js +8 -5
  96. package/lib/utils/types.js.map +1 -1
  97. package/package.json +5 -5
  98. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/README.md +0 -246
  99. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/language-switcher → _f_/_ls_}/__examples__/html-middleware-site-example.ts +0 -0
  100. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/language-switcher → _f_/_ls_}/__examples__/language-switcher-example.ts +0 -0
  101. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/language-switcher → _f_/_ls_}/build-language-url.ts +0 -0
  102. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/language-switcher → _f_/_ls_}/index.ts +0 -0
  103. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/language-switcher → _f_/_ls_}/language-switcher.html +0 -0
  104. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/language-switcher → _f_/_ls_}/language-switcher.ts +0 -0
  105. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/language-switcher → _f_/_ls_}/languages.ts +0 -0
  106. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/api/object-search.service.ts +0 -0
  107. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/breadcrumb/breadcrumb.html +0 -0
  108. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/breadcrumb/breadcrumb.ts +0 -0
  109. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/active-filters.html +0 -0
  110. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/active-filters.ts +0 -0
  111. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/date-filter.html +0 -0
  112. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/date-filter.ts +0 -0
  113. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/date-range-filter.html +0 -0
  114. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/date-range-filter.ts +0 -0
  115. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/filter-field-wrapper.html +0 -0
  116. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/filter-field-wrapper.ts +0 -0
  117. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/multi-select-filter.html +0 -0
  118. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/multi-select-filter.ts +0 -0
  119. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/numeric-range-filter.html +0 -0
  120. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/numeric-range-filter.ts +0 -0
  121. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/search-filter.html +0 -0
  122. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/search-filter.ts +0 -0
  123. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/select-filter.html +0 -0
  124. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/select-filter.ts +0 -0
  125. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/text-filter.html +0 -0
  126. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/text-filter.ts +0 -0
  127. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/object-search-state.service.ts +0 -0
  128. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/pagination/pagination.html +0 -0
  129. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/pagination/pagination.ts +0 -0
  130. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/search-bar/search-bar.html +0 -0
  131. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/search-bar/search-bar.ts +0 -0
  132. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/sort-control/sort-control.html +0 -0
  133. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/sort-control/sort-control.ts +0 -0
  134. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/utils/debounce.ts +0 -0
  135. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/utils/field-utils.ts +0 -0
  136. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/utils/filter-utils.ts +0 -0
  137. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/utils/index.ts +0 -0
  138. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/utils/sort-utils.ts +0 -0
  139. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/auth/auth.guard.ts +0 -0
  140. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/auth/auth.service.ts +0 -0
  141. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/auth-form/auth-form.html +0 -0
  142. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/auth-form/auth-form.ts +0 -0
  143. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/auth-layout/auth-layout.html +0 -0
  144. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/auth-layout/auth-layout.ts +0 -0
  145. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/card-layout/card-layout.html +0 -0
  146. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/card-layout/card-layout.ts +0 -0
  147. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/config/authentication.config.ts +0 -0
  148. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/footer-link/footer-link.html +0 -0
  149. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/footer-link/footer-link.ts +0 -0
  150. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/menu/auth-menu/auth-menu.html +0 -0
  151. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/menu/auth-menu/auth-menu.ts +0 -0
  152. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/reactive-form-page.base.ts +0 -0
  153. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/session/session.html +0 -0
  154. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/session/session.ts +0 -0
  155. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/status-alert/status-alert.html +0 -0
  156. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/status-alert/status-alert.ts +0 -0
  157. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/submit-button/submit-button.html +0 -0
  158. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/submit-button/submit-button.ts +0 -0
  159. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/utils/auth-helpers.ts +0 -0
  160. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/utils/auth-validators.ts +0 -0
  161. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/utils/helpers.ts +0 -0
  162. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/utils/session-time-servlet.ts +0 -0
  163. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/utils/session-timeout.config.ts +0 -0
  164. /package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/authentication/utils/session-timeout.service.ts +0 -0
  165. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/api/object-search.service.ts +0 -0
  166. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/breadcrumb/breadcrumb.html +0 -0
  167. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/breadcrumb/breadcrumb.ts +0 -0
  168. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/active-filters.html +0 -0
  169. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/active-filters.ts +0 -0
  170. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/date-filter.html +0 -0
  171. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/date-filter.ts +0 -0
  172. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/date-range-filter.html +0 -0
  173. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/date-range-filter.ts +0 -0
  174. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/filter-field-wrapper.html +0 -0
  175. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/filter-field-wrapper.ts +0 -0
  176. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/multi-select-filter.html +0 -0
  177. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/multi-select-filter.ts +0 -0
  178. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/numeric-range-filter.html +0 -0
  179. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/numeric-range-filter.ts +0 -0
  180. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/search-filter.html +0 -0
  181. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/search-filter.ts +0 -0
  182. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/select-filter.html +0 -0
  183. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/select-filter.ts +0 -0
  184. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/text-filter.html +0 -0
  185. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/filters/text-filter.ts +0 -0
  186. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/object-search-state.service.ts +0 -0
  187. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/pagination/pagination.html +0 -0
  188. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/pagination/pagination.ts +0 -0
  189. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/search-bar/search-bar.html +0 -0
  190. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/search-bar/search-bar.ts +0 -0
  191. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/sort-control/sort-control.html +0 -0
  192. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/sort-control/sort-control.ts +0 -0
  193. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/utils/debounce.ts +0 -0
  194. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/utils/field-utils.ts +0 -0
  195. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/utils/filter-utils.ts +0 -0
  196. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/utils/index.ts +0 -0
  197. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features/object-search → _f_/_os_}/utils/sort-utils.ts +0 -0
  198. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/agentforce/agentforce-embed.service.ts +0 -0
  199. /package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/{features → _f_}/agentforce/conversation.html +0 -0
  200. /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.64.0",
22
- "@salesforce/ui-bundle": "^11.64.0",
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",
@@ -46,8 +47,8 @@
46
47
  "@graphql-eslint/eslint-plugin": "^4.1.0",
47
48
  "@graphql-tools/utils": "^11.0.0",
48
49
  "@playwright/test": "^1.49.0",
49
- "@salesforce/graphiti": "^11.64.0",
50
- "@salesforce/vite-plugin-ui-bundle": "^11.64.0",
50
+ "@salesforce/graphiti": "^12.4.5",
51
+ "@salesforce/vite-plugin-ui-bundle": "^12.4.5",
51
52
  "@testing-library/jest-dom": "^6.6.3",
52
53
  "@testing-library/react": "^16.1.0",
53
54
  "@testing-library/user-event": "^14.5.2",
@@ -64,6 +65,7 @@
64
65
  "graphql": "^16.11.0",
65
66
  "graphql-codegen-typescript-operation-types": "^2.0.2",
66
67
  "jsdom": "^25.0.1",
68
+ "patch-package": "^8.0.1",
67
69
  "serve": "^14.2.5",
68
70
  "typescript": "~5.9.3",
69
71
  "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
+ /**
@@ -46,8 +46,9 @@ export default function Login() {
46
46
  });
47
47
  const result = await handleApiResponse<AuthResponse>(response);
48
48
  if (result?.redirectUrl) {
49
- // Hard navigate to the URL which establishes the server session cookie
50
- window.location.replace(result.redirectUrl);
49
+ // Hard navigate with assign (not replace) so /login stays in history and the
50
+ // browser Back button returns here after an MFA/verification screen.
51
+ window.location.assign(result.redirectUrl);
51
52
  } else {
52
53
  // In case redirectUrl is null, navigate to home
53
54
  navigate("/", { replace: true });
@@ -58,8 +58,9 @@ export default function Register() {
58
58
  });
59
59
  const result = await handleApiResponse<AuthResponse>(response);
60
60
  if (result?.redirectUrl) {
61
- // Hard navigate to the URL which logs the new user in
62
- window.location.replace(result.redirectUrl);
61
+ // Hard navigate with assign (not replace) so this page stays in history and
62
+ // the browser Back button returns here after an MFA/verification screen.
63
+ window.location.assign(result.redirectUrl);
63
64
  } else {
64
65
  // In case redirectUrl is null, redirect to the login page
65
66
  navigate(ROUTES.LOGIN.PATH, { replace: true });
@@ -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
  )}
@@ -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
@@ -208,12 +208,17 @@ function toEndOfDay(d: string) {
208
208
  return `${d}T23:59:59.999Z`;
209
209
  }
210
210
 
211
+ /** Escapes LIKE metacharacters (`\`, `%`, `_`) so user input is matched literally. */
212
+ function escapeLikePattern(value: string): string {
213
+ return value.replace(/[\\%_]/g, "\\$&");
214
+ }
215
+
211
216
  function buildSingleFilter(filter: ActiveFilterValue, config?: FilterFieldConfig): unknown {
212
217
  const { field, type, value, min, max } = filter;
213
218
  switch (type) {
214
219
  case "text": {
215
220
  if (!value) return null;
216
- return { [field]: { like: `%${value}%` } };
221
+ return { [field]: { like: `%${escapeLikePattern(value)}%` } };
217
222
  }
218
223
  case "picklist": {
219
224
  if (!value) return null;
@@ -228,8 +233,13 @@ function buildSingleFilter(filter: ActiveFilterValue, config?: FilterFieldConfig
228
233
  if (config?.max != null) n = Math.min(n, config.max);
229
234
  return n;
230
235
  };
231
- if (min) ops.gte = clamp(Number(min));
232
- if (max) ops.lte = clamp(Number(max));
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;
233
243
  return { [field]: ops };
234
244
  }
235
245
  case "boolean": {
@@ -284,7 +294,7 @@ export function buildGlobalQueryClause(q: string, searchableFields: string[]): u
284
294
  if (!trimmed || searchableFields.length === 0) return undefined;
285
295
  const clauses = searchableFields.map((path) => {
286
296
  const parts = path.split(".");
287
- let clause: Record<string, unknown> = { like: `%${trimmed}%` };
297
+ let clause: Record<string, unknown> = { like: `%${escapeLikePattern(trimmed)}%` };
288
298
  for (let i = parts.length - 1; i >= 0; i--) {
289
299
  clause = { [parts[i]]: clause };
290
300
  }
@@ -1,6 +1,6 @@
1
1
  import { Outlet, Link, useLocation } from "react-router";
2
2
  import { getAllRoutes } from "./router-utils";
3
- import { useState } from "react";
3
+ import { useEffect, useId, useRef, useState } from "react";
4
4
  import { AuthMenu } from "./features/authentication/menu/AuthMenu";
5
5
  import { Button } from "./components/ui/button";
6
6
  import CustomerWebClientChat from "./components/CustomerWebClientChat";
@@ -8,6 +8,21 @@ import CustomerWebClientChat from "./components/CustomerWebClientChat";
8
8
  export default function AppLayout() {
9
9
  const [isOpen, setIsOpen] = useState(false);
10
10
  const location = useLocation();
11
+ const navId = useId();
12
+ const outletWrapperRef = useRef<HTMLDivElement>(null);
13
+ const previousPathname = useRef<string | null>(null);
14
+
15
+ useEffect(() => {
16
+ if (previousPathname.current !== null && previousPathname.current !== location.pathname) {
17
+ const wrapper = outletWrapperRef.current;
18
+ const target = wrapper?.querySelector<HTMLElement>("h1, main, [role='main']") ?? wrapper;
19
+ if (target) {
20
+ if (!target.hasAttribute("tabindex")) target.setAttribute("tabindex", "-1");
21
+ target.focus();
22
+ }
23
+ }
24
+ previousPathname.current = location.pathname;
25
+ }, [location.pathname]);
11
26
 
12
27
  const isActive = (path: string) => location.pathname === path;
13
28
 
@@ -30,6 +45,20 @@ export default function AppLayout() {
30
45
 
31
46
  return (
32
47
  <>
48
+ <a
49
+ href="#main-content"
50
+ onClick={(e) => {
51
+ e.preventDefault();
52
+ const content = outletWrapperRef.current;
53
+ if (content) {
54
+ if (!content.hasAttribute("tabindex")) content.setAttribute("tabindex", "-1");
55
+ content.focus();
56
+ }
57
+ }}
58
+ 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"
59
+ >
60
+ Skip to main content
61
+ </a>
33
62
  <nav className="bg-white border-b border-gray-200">
34
63
  <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
35
64
  <div className="flex justify-between items-center h-16">
@@ -44,8 +73,12 @@ export default function AppLayout() {
44
73
  onClick={toggleMenu}
45
74
  aria-label="Toggle menu"
46
75
  aria-expanded={isOpen}
76
+ aria-controls={navId}
47
77
  >
48
- <div className="w-6 h-6 flex flex-col justify-center space-y-1.5">
78
+ <div
79
+ aria-hidden="true"
80
+ className="w-6 h-6 flex flex-col justify-center space-y-1.5"
81
+ >
49
82
  <span
50
83
  className={`block h-0.5 w-6 bg-current transition-all ${
51
84
  isOpen ? "rotate-45 translate-y-2" : ""
@@ -63,8 +96,8 @@ export default function AppLayout() {
63
96
  </Button>
64
97
  </div>
65
98
  </div>
66
- {isOpen && (
67
- <div className="pb-4">
99
+ <div id={navId} className="pb-4" hidden={!isOpen}>
100
+ {isOpen && (
68
101
  <div className="flex flex-col space-y-1">
69
102
  {navigationRoutes.map((item) => (
70
103
  <Button
@@ -79,11 +112,13 @@ export default function AppLayout() {
79
112
  </Button>
80
113
  ))}
81
114
  </div>
82
- </div>
83
- )}
115
+ )}
116
+ </div>
84
117
  </div>
85
118
  </nav>
86
- <Outlet />
119
+ <div id="main-content" ref={outletWrapperRef}>
120
+ <Outlet />
121
+ </div>
87
122
  <CustomerWebClientChat
88
123
  orgId="<ORG_ID_15_OR_18_CHAR_00D...>"
89
124
  deploymentName="<EMBEDDED_SERVICE_DEPLOYMENT_NAME>"
@@ -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;