@stapel/search-react 0.1.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 (195) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/MODULE.md +203 -0
  3. package/README.md +175 -0
  4. package/dist/api/generated/schema.d.ts +375 -0
  5. package/dist/api/generated/schema.d.ts.map +1 -0
  6. package/dist/api/generated/schema.js +2 -0
  7. package/dist/api/generated/schema.js.map +1 -0
  8. package/dist/api/searchApi.d.ts +77 -0
  9. package/dist/api/searchApi.d.ts.map +1 -0
  10. package/dist/api/searchApi.js +79 -0
  11. package/dist/api/searchApi.js.map +1 -0
  12. package/dist/api/types.d.ts +131 -0
  13. package/dist/api/types.d.ts.map +1 -0
  14. package/dist/api/types.js +20 -0
  15. package/dist/api/types.js.map +1 -0
  16. package/dist/default/DegradationNotice.d.ts +19 -0
  17. package/dist/default/DegradationNotice.d.ts.map +1 -0
  18. package/dist/default/DegradationNotice.js +14 -0
  19. package/dist/default/DegradationNotice.js.map +1 -0
  20. package/dist/default/ErrorAlert.d.ts +9 -0
  21. package/dist/default/ErrorAlert.d.ts.map +1 -0
  22. package/dist/default/ErrorAlert.js +27 -0
  23. package/dist/default/ErrorAlert.js.map +1 -0
  24. package/dist/default/FacetPanelPane.d.ts +26 -0
  25. package/dist/default/FacetPanelPane.d.ts.map +1 -0
  26. package/dist/default/FacetPanelPane.js +32 -0
  27. package/dist/default/FacetPanelPane.js.map +1 -0
  28. package/dist/default/RankingDisclosurePane.d.ts +17 -0
  29. package/dist/default/RankingDisclosurePane.d.ts.map +1 -0
  30. package/dist/default/RankingDisclosurePane.js +23 -0
  31. package/dist/default/RankingDisclosurePane.js.map +1 -0
  32. package/dist/default/SearchPage.d.ts +34 -0
  33. package/dist/default/SearchPage.d.ts.map +1 -0
  34. package/dist/default/SearchPage.js +16 -0
  35. package/dist/default/SearchPage.js.map +1 -0
  36. package/dist/default/SearchResultCard.d.ts +34 -0
  37. package/dist/default/SearchResultCard.d.ts.map +1 -0
  38. package/dist/default/SearchResultCard.js +38 -0
  39. package/dist/default/SearchResultCard.js.map +1 -0
  40. package/dist/default/SearchResultsPane.d.ts +32 -0
  41. package/dist/default/SearchResultsPane.d.ts.map +1 -0
  42. package/dist/default/SearchResultsPane.js +46 -0
  43. package/dist/default/SearchResultsPane.js.map +1 -0
  44. package/dist/default/SortSelect.d.ts +22 -0
  45. package/dist/default/SortSelect.d.ts.map +1 -0
  46. package/dist/default/SortSelect.js +39 -0
  47. package/dist/default/SortSelect.js.map +1 -0
  48. package/dist/default/UrlIssueNotice.d.ts +12 -0
  49. package/dist/default/UrlIssueNotice.d.ts.map +1 -0
  50. package/dist/default/UrlIssueNotice.js +13 -0
  51. package/dist/default/UrlIssueNotice.js.map +1 -0
  52. package/dist/default/index.d.ts +40 -0
  53. package/dist/default/index.d.ts.map +1 -0
  54. package/dist/default/index.js +34 -0
  55. package/dist/default/index.js.map +1 -0
  56. package/dist/default/theme.d.ts +24 -0
  57. package/dist/default/theme.d.ts.map +1 -0
  58. package/dist/default/theme.js +39 -0
  59. package/dist/default/theme.js.map +1 -0
  60. package/dist/default/types.d.ts +12 -0
  61. package/dist/default/types.d.ts.map +1 -0
  62. package/dist/default/types.js +2 -0
  63. package/dist/default/types.js.map +1 -0
  64. package/dist/headless/FacetPanel.d.ts +61 -0
  65. package/dist/headless/FacetPanel.d.ts.map +1 -0
  66. package/dist/headless/FacetPanel.js +57 -0
  67. package/dist/headless/FacetPanel.js.map +1 -0
  68. package/dist/headless/RankingDisclosure.d.ts +39 -0
  69. package/dist/headless/RankingDisclosure.d.ts.map +1 -0
  70. package/dist/headless/RankingDisclosure.js +28 -0
  71. package/dist/headless/RankingDisclosure.js.map +1 -0
  72. package/dist/headless/SearchProvider.d.ts +19 -0
  73. package/dist/headless/SearchProvider.d.ts.map +1 -0
  74. package/dist/headless/SearchProvider.js +15 -0
  75. package/dist/headless/SearchProvider.js.map +1 -0
  76. package/dist/headless/SearchResults.d.ts +68 -0
  77. package/dist/headless/SearchResults.d.ts.map +1 -0
  78. package/dist/headless/SearchResults.js +70 -0
  79. package/dist/headless/SearchResults.js.map +1 -0
  80. package/dist/headless/SearchStateProvider.d.ts +69 -0
  81. package/dist/headless/SearchStateProvider.d.ts.map +1 -0
  82. package/dist/headless/SearchStateProvider.js +71 -0
  83. package/dist/headless/SearchStateProvider.js.map +1 -0
  84. package/dist/i18n/errorsMap.d.ts +27 -0
  85. package/dist/i18n/errorsMap.d.ts.map +1 -0
  86. package/dist/i18n/errorsMap.js +37 -0
  87. package/dist/i18n/errorsMap.js.map +1 -0
  88. package/dist/i18n/es.d.ts +13 -0
  89. package/dist/i18n/es.d.ts.map +1 -0
  90. package/dist/i18n/es.js +85 -0
  91. package/dist/i18n/es.js.map +1 -0
  92. package/dist/i18n/generated/errors.es.gen.d.ts +9 -0
  93. package/dist/i18n/generated/errors.es.gen.d.ts.map +1 -0
  94. package/dist/i18n/generated/errors.es.gen.js +63 -0
  95. package/dist/i18n/generated/errors.es.gen.js.map +1 -0
  96. package/dist/i18n/generated/errors.gen.d.ts +359 -0
  97. package/dist/i18n/generated/errors.gen.d.ts.map +1 -0
  98. package/dist/i18n/generated/errors.gen.js +183 -0
  99. package/dist/i18n/generated/errors.gen.js.map +1 -0
  100. package/dist/i18n/generated/errors.ru.gen.d.ts +9 -0
  101. package/dist/i18n/generated/errors.ru.gen.d.ts.map +1 -0
  102. package/dist/i18n/generated/errors.ru.gen.js +63 -0
  103. package/dist/i18n/generated/errors.ru.gen.js.map +1 -0
  104. package/dist/i18n/keys.d.ts +107 -0
  105. package/dist/i18n/keys.d.ts.map +1 -0
  106. package/dist/i18n/keys.js +185 -0
  107. package/dist/i18n/keys.js.map +1 -0
  108. package/dist/i18n/ru.d.ts +29 -0
  109. package/dist/i18n/ru.d.ts.map +1 -0
  110. package/dist/i18n/ru.js +101 -0
  111. package/dist/i18n/ru.js.map +1 -0
  112. package/dist/index.d.ts +72 -0
  113. package/dist/index.d.ts.map +1 -0
  114. package/dist/index.js +66 -0
  115. package/dist/index.js.map +1 -0
  116. package/dist/model/context.d.ts +11 -0
  117. package/dist/model/context.d.ts.map +1 -0
  118. package/dist/model/context.js +16 -0
  119. package/dist/model/context.js.map +1 -0
  120. package/dist/model/queries.d.ts +48 -0
  121. package/dist/model/queries.d.ts.map +1 -0
  122. package/dist/model/queries.js +65 -0
  123. package/dist/model/queries.js.map +1 -0
  124. package/dist/model/queryKeys.d.ts +25 -0
  125. package/dist/model/queryKeys.d.ts.map +1 -0
  126. package/dist/model/queryKeys.js +22 -0
  127. package/dist/model/queryKeys.js.map +1 -0
  128. package/dist/model/runtime.d.ts +25 -0
  129. package/dist/model/runtime.d.ts.map +1 -0
  130. package/dist/model/runtime.js +6 -0
  131. package/dist/model/runtime.js.map +1 -0
  132. package/dist/nav/manifest.d.ts +21 -0
  133. package/dist/nav/manifest.d.ts.map +1 -0
  134. package/dist/nav/manifest.js +34 -0
  135. package/dist/nav/manifest.js.map +1 -0
  136. package/dist/router/index.d.ts +10 -0
  137. package/dist/router/index.d.ts.map +1 -0
  138. package/dist/router/index.js +34 -0
  139. package/dist/router/index.js.map +1 -0
  140. package/dist/state/degradations.d.ts +30 -0
  141. package/dist/state/degradations.d.ts.map +1 -0
  142. package/dist/state/degradations.js +52 -0
  143. package/dist/state/degradations.js.map +1 -0
  144. package/dist/state/facets.d.ts +68 -0
  145. package/dist/state/facets.d.ts.map +1 -0
  146. package/dist/state/facets.js +171 -0
  147. package/dist/state/facets.js.map +1 -0
  148. package/dist/state/urlState.d.ts +143 -0
  149. package/dist/state/urlState.d.ts.map +1 -0
  150. package/dist/state/urlState.js +319 -0
  151. package/dist/state/urlState.js.map +1 -0
  152. package/llms.txt +87 -0
  153. package/manifest.json +652 -0
  154. package/nav-manifest.json +44 -0
  155. package/package.json +133 -0
  156. package/src/analytics/generated/events.json +7 -0
  157. package/src/api/generated/schema.ts +377 -0
  158. package/src/api/searchApi.ts +169 -0
  159. package/src/api/types.ts +160 -0
  160. package/src/default/DegradationNotice.tsx +50 -0
  161. package/src/default/ErrorAlert.tsx +46 -0
  162. package/src/default/FacetPanelPane.tsx +174 -0
  163. package/src/default/RankingDisclosurePane.tsx +116 -0
  164. package/src/default/SearchPage.tsx +87 -0
  165. package/src/default/SearchResultCard.tsx +96 -0
  166. package/src/default/SearchResultsPane.tsx +173 -0
  167. package/src/default/SortSelect.tsx +77 -0
  168. package/src/default/UrlIssueNotice.tsx +37 -0
  169. package/src/default/index.ts +48 -0
  170. package/src/default/theme.tsx +66 -0
  171. package/src/default/types.ts +12 -0
  172. package/src/headless/FacetPanel.tsx +113 -0
  173. package/src/headless/RankingDisclosure.tsx +58 -0
  174. package/src/headless/SearchProvider.tsx +20 -0
  175. package/src/headless/SearchResults.tsx +149 -0
  176. package/src/headless/SearchStateProvider.tsx +175 -0
  177. package/src/i18n/errorsMap.ts +50 -0
  178. package/src/i18n/es.ts +110 -0
  179. package/src/i18n/generated/errors.es.gen.ts +69 -0
  180. package/src/i18n/generated/errors.gen.ts +214 -0
  181. package/src/i18n/generated/errors.json +423 -0
  182. package/src/i18n/generated/errors.ru.gen.ts +69 -0
  183. package/src/i18n/keys.ts +220 -0
  184. package/src/i18n/ru.ts +126 -0
  185. package/src/index.ts +154 -0
  186. package/src/model/context.tsx +28 -0
  187. package/src/model/queries.ts +79 -0
  188. package/src/model/queryKeys.ts +48 -0
  189. package/src/model/runtime.ts +33 -0
  190. package/src/nav/manifest.ts +53 -0
  191. package/src/router/index.ts +38 -0
  192. package/src/state/degradations.ts +75 -0
  193. package/src/state/facets.ts +219 -0
  194. package/src/state/urlState.ts +458 -0
  195. package/tsconfig.json +26 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/model/context.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAErC,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAYlD,eAAO,MAAM,oBAAoB,EAAE,OAAO,CAAC,aAAa,GAAG,IAAI,CAC3C,CAAC;AAErB,eAAO,MAAM,gBAAgB,EAAE,MAAM,aAA8B,CAAC;AAEpE,eAAO,MAAM,YAAY,EAAE,MAAM,SAAsB,CAAC;AAExD,eAAO,MAAM,kBAAkB,EAAE,MAAM,SAAS,GAAG,IAAuB,CAAC;AAE3E,oEAAoE;AACpE,eAAO,MAAM,cAAc,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAC,UAAU,CACzD,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { createModuleContext } from "@stapel/core";
2
+ /**
3
+ * The wired SearchRuntime shared through React context by
4
+ * `<SearchProvider>`. Hooks in `model/` and `headless/` read the singletons
5
+ * from here. One reviewed copy of this plumbing lives in `@stapel/core`
6
+ * (`createModuleContext`, slim wave §21/S2); this module binds it under the
7
+ * pair's public names.
8
+ */
9
+ const kit = createModuleContext("Search");
10
+ export const SearchRuntimeContext = kit.RuntimeContext;
11
+ export const useSearchRuntime = kit.useRuntime;
12
+ export const useSearchApi = kit.useApi;
13
+ export const useSearchAnalytics = kit.useAnalytics;
14
+ /** @internal Re-exported as `<SearchProvider>` from `headless/`. */
15
+ export const ModuleProvider = kit.Provider;
16
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/model/context.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAKnD;;;;;;GAMG;AACH,MAAM,GAAG,GACP,mBAAmB,CAAgB,QAAQ,CAAC,CAAC;AAE/C,MAAM,CAAC,MAAM,oBAAoB,GAC/B,GAAG,CAAC,cAAc,CAAC;AAErB,MAAM,CAAC,MAAM,gBAAgB,GAAwB,GAAG,CAAC,UAAU,CAAC;AAEpE,MAAM,CAAC,MAAM,YAAY,GAAoB,GAAG,CAAC,MAAM,CAAC;AAExD,MAAM,CAAC,MAAM,kBAAkB,GAA2B,GAAG,CAAC,YAAY,CAAC;AAE3E,oEAAoE;AACpE,MAAM,CAAC,MAAM,cAAc,GACzB,GAAG,CAAC,QAAQ,CAAC"}
@@ -0,0 +1,48 @@
1
+ import type { UseQueryResult } from "@tanstack/react-query";
2
+ import type { StapelApiError } from "@stapel/core";
3
+ import type { RankingResponse, SearchQueryState, SearchResponse } from "../api/types.js";
4
+ /**
5
+ * Read hooks over the search API (frontend-standard §2). Keys are namespaced
6
+ * (see `searchQueryKeys`).
7
+ *
8
+ * NOT SESSION-GATED, and that is the documented exception rather than an
9
+ * omission. Sibling pairs gate every read on `useActiveSessionReady` because
10
+ * their endpoints need a principal; these three are `AllowAny`, and core's own
11
+ * doc comment carves out exactly this case ("or be unconditionally safe
12
+ * pre-session, e.g. a public GET"). Gating them would make a storefront's
13
+ * catalogue wait for a login bootstrap that a visitor who will never sign in
14
+ * has no stake in — a blank shop front for the length of a token refresh.
15
+ */
16
+ /**
17
+ * One keyset page of results for a search state.
18
+ *
19
+ * `placeholderData: keepPreviousData`, and it is not a nicety. Facets are
20
+ * DRILL-DOWN: the whole point of the panel is that choosing a value leaves its
21
+ * siblings with the counts you would get by switching to them. A panel that
22
+ * blanks to a spinner between every click cannot show that — the person sees
23
+ * the numbers vanish and reappear, which reads exactly like the naive facets
24
+ * this contract avoids. The previous answer therefore stays on screen while
25
+ * the next one is in flight, with `isFetching` telling a skin to dim it.
26
+ *
27
+ * The load discipline is intact: the FIRST load has nothing to keep and is
28
+ * `loading`, and a failure still lands as `failed` rather than leaving stale
29
+ * rows pretending to be current.
30
+ *
31
+ * `retry: false`: the refusals here are VERDICTS about the request, not blips
32
+ * — an unknown sort, a malformed range, a cursor past `MAX_RESULT_WINDOW`.
33
+ * Retrying a 400 three times only delays the moment the page can say which
34
+ * one it was. A real outage answers 503 and is retried by the person, through
35
+ * the bag's `refetch` (the spec's §7.4 negative leg: "we could not ask" plus a
36
+ * retry, never "nothing found").
37
+ */
38
+ export declare function useSearchQuery(state: SearchQueryState, options?: {
39
+ readonly enabled?: boolean;
40
+ }): UseQueryResult<SearchResponse, StapelApiError>;
41
+ /**
42
+ * The P2B Art. 5 ranking disclosure.
43
+ *
44
+ * `staleTime: Infinity`: the scorer registry changes when somebody deploys,
45
+ * not while a person reads the page it explains.
46
+ */
47
+ export declare function useRankingDisclosure(type?: string): UseQueryResult<RankingResponse, StapelApiError>;
48
+ //# sourceMappingURL=queries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../../src/model/queries.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EAChB,cAAc,EACf,MAAM,iBAAiB,CAAC;AAIzB;;;;;;;;;;;GAWG;AAEH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,gBAAgB,EACvB,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GACvC,cAAc,CAAC,cAAc,EAAE,cAAc,CAAC,CAUhD;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,CAAC,EAAE,MAAM,GACZ,cAAc,CAAC,eAAe,EAAE,cAAc,CAAC,CAQjD"}
@@ -0,0 +1,65 @@
1
+ import { keepPreviousData, useQuery } from "@tanstack/react-query";
2
+ import { searchQueryParams } from "../api/searchApi.js";
3
+ import { useSearchApi } from "./context.js";
4
+ import { searchQueryKeys } from "./queryKeys.js";
5
+ /**
6
+ * Read hooks over the search API (frontend-standard §2). Keys are namespaced
7
+ * (see `searchQueryKeys`).
8
+ *
9
+ * NOT SESSION-GATED, and that is the documented exception rather than an
10
+ * omission. Sibling pairs gate every read on `useActiveSessionReady` because
11
+ * their endpoints need a principal; these three are `AllowAny`, and core's own
12
+ * doc comment carves out exactly this case ("or be unconditionally safe
13
+ * pre-session, e.g. a public GET"). Gating them would make a storefront's
14
+ * catalogue wait for a login bootstrap that a visitor who will never sign in
15
+ * has no stake in — a blank shop front for the length of a token refresh.
16
+ */
17
+ /**
18
+ * One keyset page of results for a search state.
19
+ *
20
+ * `placeholderData: keepPreviousData`, and it is not a nicety. Facets are
21
+ * DRILL-DOWN: the whole point of the panel is that choosing a value leaves its
22
+ * siblings with the counts you would get by switching to them. A panel that
23
+ * blanks to a spinner between every click cannot show that — the person sees
24
+ * the numbers vanish and reappear, which reads exactly like the naive facets
25
+ * this contract avoids. The previous answer therefore stays on screen while
26
+ * the next one is in flight, with `isFetching` telling a skin to dim it.
27
+ *
28
+ * The load discipline is intact: the FIRST load has nothing to keep and is
29
+ * `loading`, and a failure still lands as `failed` rather than leaving stale
30
+ * rows pretending to be current.
31
+ *
32
+ * `retry: false`: the refusals here are VERDICTS about the request, not blips
33
+ * — an unknown sort, a malformed range, a cursor past `MAX_RESULT_WINDOW`.
34
+ * Retrying a 400 three times only delays the moment the page can say which
35
+ * one it was. A real outage answers 503 and is retried by the person, through
36
+ * the bag's `refetch` (the spec's §7.4 negative leg: "we could not ask" plus a
37
+ * retry, never "nothing found").
38
+ */
39
+ export function useSearchQuery(state, options) {
40
+ const api = useSearchApi();
41
+ const params = searchQueryParams(state);
42
+ return useQuery({
43
+ queryKey: searchQueryKeys.query(params),
44
+ queryFn: ({ signal }) => api.query(state, { signal }),
45
+ enabled: (options?.enabled ?? true) && state.type.length > 0,
46
+ placeholderData: keepPreviousData,
47
+ retry: false,
48
+ });
49
+ }
50
+ /**
51
+ * The P2B Art. 5 ranking disclosure.
52
+ *
53
+ * `staleTime: Infinity`: the scorer registry changes when somebody deploys,
54
+ * not while a person reads the page it explains.
55
+ */
56
+ export function useRankingDisclosure(type) {
57
+ const api = useSearchApi();
58
+ return useQuery({
59
+ queryKey: searchQueryKeys.ranking(type),
60
+ queryFn: ({ signal }) => api.ranking(type, { signal }),
61
+ staleTime: Number.POSITIVE_INFINITY,
62
+ retry: false,
63
+ });
64
+ }
65
+ //# sourceMappingURL=queries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queries.js","sourceRoot":"","sources":["../../src/model/queries.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAGnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAMxD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEjD;;;;;;;;;;;GAWG;AAEH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,cAAc,CAC5B,KAAuB,EACvB,OAAwC;IAExC,MAAM,GAAG,GAAG,YAAY,EAAE,CAAC;IAC3B,MAAM,MAAM,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;IACxC,OAAO,QAAQ,CAAC;QACd,QAAQ,EAAE,eAAe,CAAC,KAAK,CAAC,MAAM,CAAC;QACvC,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,CAAC;QACrD,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;QAC5D,eAAe,EAAE,gBAAgB;QACjC,KAAK,EAAE,KAAK;KACb,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,IAAa;IAEb,MAAM,GAAG,GAAG,YAAY,EAAE,CAAC;IAC3B,OAAO,QAAQ,CAAC;QACd,QAAQ,EAAE,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC;QACvC,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,CAAC;QACtD,SAAS,EAAE,MAAM,CAAC,iBAAiB;QACnC,KAAK,EAAE,KAAK;KACb,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Namespaced TanStack Query keys (frontend-standard §2 — namespaced keys).
3
+ * Everything under the `"search"` root so a host can invalidate the whole
4
+ * module or match a single read. Explicit tuple return types satisfy
5
+ * `--isolatedDeclarations`.
6
+ *
7
+ * THE KEY IS THE REQUEST. `query()` is keyed on the very object
8
+ * `searchQueryParams()` hands the client, not on a hand-picked subset — so a
9
+ * parameter that changes the URL and the request but not the key (the stale
10
+ * page bug: new filter, cached rows) cannot be written. TanStack hashes keys
11
+ * with sorted-key JSON, so two equal states hash equal regardless of the order
12
+ * the members were built in, and one cursor's page stays cached under its own
13
+ * key — which is what makes "back" instant.
14
+ */
15
+ /** The wire query object a search read is keyed on. */
16
+ export type SearchQueryKeyParams = Readonly<Record<string, string | number | boolean | undefined | readonly (string | number | boolean)[]>>;
17
+ export declare const searchQueryKeys: {
18
+ readonly all: readonly ["search"];
19
+ query(params: SearchQueryKeyParams): readonly ["search", "query", SearchQueryKeyParams];
20
+ /** The P2B disclosure. `type` is optional on the endpoint; normalized to
21
+ * `null` so an absent type and an explicit `undefined` cannot cache twice. */
22
+ ranking(type?: string): readonly ["search", "ranking", string | null];
23
+ suggest(type: string, q: string, limit?: number): readonly ["search", "suggest", string, string, number | null];
24
+ };
25
+ //# sourceMappingURL=queryKeys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queryKeys.d.ts","sourceRoot":"","sources":["../../src/model/queryKeys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,uDAAuD;AACvD,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CACzC,MAAM,CACJ,MAAM,EACJ,MAAM,GACN,MAAM,GACN,OAAO,GACP,SAAS,GACT,SAAS,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,EAAE,CACzC,CACF,CAAC;AAIF,eAAO,MAAM,eAAe,EAAE;IAC5B,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC,QAAQ,CAAC,CAAC;IAClC,KAAK,CACH,MAAM,EAAE,oBAAoB,GAC3B,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,oBAAoB,CAAC,CAAC;IACtD;kFAC8E;IAC9E,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;IACtE,OAAO,CACL,IAAI,EAAE,MAAM,EACZ,CAAC,EAAE,MAAM,EACT,KAAK,CAAC,EAAE,MAAM,GACb,SAAS,CAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;CAMlE,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Namespaced TanStack Query keys (frontend-standard §2 — namespaced keys).
3
+ * Everything under the `"search"` root so a host can invalidate the whole
4
+ * module or match a single read. Explicit tuple return types satisfy
5
+ * `--isolatedDeclarations`.
6
+ *
7
+ * THE KEY IS THE REQUEST. `query()` is keyed on the very object
8
+ * `searchQueryParams()` hands the client, not on a hand-picked subset — so a
9
+ * parameter that changes the URL and the request but not the key (the stale
10
+ * page bug: new filter, cached rows) cannot be written. TanStack hashes keys
11
+ * with sorted-key JSON, so two equal states hash equal regardless of the order
12
+ * the members were built in, and one cursor's page stays cached under its own
13
+ * key — which is what makes "back" instant.
14
+ */
15
+ const ROOT = "search";
16
+ export const searchQueryKeys = {
17
+ all: [ROOT],
18
+ query: (params) => [ROOT, "query", params],
19
+ ranking: (type) => [ROOT, "ranking", type ?? null],
20
+ suggest: (type, q, limit) => [ROOT, "suggest", type, q, limit ?? null],
21
+ };
22
+ //# sourceMappingURL=queryKeys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queryKeys.js","sourceRoot":"","sources":["../../src/model/queryKeys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAcH,MAAM,IAAI,GAAG,QAAiB,CAAC;AAE/B,MAAM,CAAC,MAAM,eAAe,GAaxB;IACF,GAAG,EAAE,CAAC,IAAI,CAAC;IACX,KAAK,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC;IAC1C,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,IAAI,IAAI,CAAC;IAClD,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,IAAI,IAAI,CAAC;CACvE,CAAC"}
@@ -0,0 +1,25 @@
1
+ import type { CreateModuleRuntimeOptions, ModuleRuntime } from "@stapel/core";
2
+ import type { SearchApi } from "../api/searchApi.js";
3
+ /**
4
+ * The wired search runtime — core's `ModuleRuntime` bound to this pair's API
5
+ * (slim wave §21/S2). The returned `client` is what the host injects into
6
+ * core's `StapelConfigProvider` (as the default or the `"search"` module
7
+ * client), preserving the client-injection fork seam (frontend-standard §7.2).
8
+ *
9
+ * ANONYMOUS BY DESIGN. Every endpoint this pair calls is `AllowAny`, so a
10
+ * storefront's catalogue, category and search pages need nothing but:
11
+ *
12
+ * ```tsx
13
+ * const runtime = createSearchRuntime({ baseUrl: "/search/api/v1/" });
14
+ * <SearchProvider runtime={runtime}>…</SearchProvider>
15
+ * ```
16
+ *
17
+ * No session, no workspace id, no auth client. When a session DOES exist the
18
+ * host's auth runtime supplies the token on the shared client and the same
19
+ * calls carry it — this pair neither requires nor waits for one, which is why
20
+ * its read hooks are deliberately not gated on `useActiveSessionReady`.
21
+ */
22
+ export type SearchRuntime = ModuleRuntime<SearchApi>;
23
+ export type CreateSearchRuntimeOptions = CreateModuleRuntimeOptions;
24
+ export declare function createSearchRuntime(options: CreateSearchRuntimeOptions): SearchRuntime;
25
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/model/runtime.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,0BAA0B,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,aAAa,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;AAErD,MAAM,MAAM,0BAA0B,GAAG,0BAA0B,CAAC;AAEpE,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,0BAA0B,GAClC,aAAa,CAEf"}
@@ -0,0 +1,6 @@
1
+ import { createModuleRuntime } from "@stapel/core";
2
+ import { createSearchApi } from "../api/searchApi.js";
3
+ export function createSearchRuntime(options) {
4
+ return createModuleRuntime((client) => createSearchApi(client), options);
5
+ }
6
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/model/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEnD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AA0BtD,MAAM,UAAU,mBAAmB,CACjC,OAAmC;IAEnC,OAAO,mBAAmB,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC;AAC3E,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * This pair's contribution to the scripted-fullstack nav contract
3
+ * (`@stapel/core`'s `NavEntry`/`PackageNavManifest`). `scripts/gen-nav-
4
+ * manifest.mjs` reads `navEntries` below, stamps `package`/`version` from THIS
5
+ * package's own `package.json`, and emits
6
+ * `packages/search-react/nav-manifest.json` plus this package's slice of the
7
+ * root aggregate.
8
+ *
9
+ * Both entries declare `surface: "public"` EXPLICITLY. The derivation
10
+ * `requiresAuth ? "member" : "public"` would land on the same answer here, but
11
+ * the explicit declaration is what a public container can rely on: an entry
12
+ * that later gains `requiresAuth` for an unrelated reason must not silently
13
+ * fall out of the anonymous tree (`core/src/nav.ts`, `navEntrySurface`).
14
+ *
15
+ * `menuVisibleDefault: false` on both: the results page is a navigation TARGET
16
+ * reached from the header's search box, and the ranking disclosure is a
17
+ * footer link. Neither is a menu item — the same treatment `auth.login` gets.
18
+ */
19
+ import type { NavEntry } from "@stapel/core";
20
+ export declare const navEntries: readonly NavEntry[];
21
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/nav/manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE7C,eAAO,MAAM,UAAU,EAAE,SAAS,QAAQ,EAgCzC,CAAC"}
@@ -0,0 +1,34 @@
1
+ export const navEntries = [
2
+ {
3
+ id: "search.results",
4
+ labelKey: "search.results.title",
5
+ icon: "SearchOutlined",
6
+ route: { path: "/s" },
7
+ component: { export: "SearchPage", subpath: "default" },
8
+ placement: { level: "top" },
9
+ menuVisibleDefault: false,
10
+ requiresAuth: false,
11
+ surface: "public",
12
+ order: 10,
13
+ },
14
+ {
15
+ // The P2B Art. 5 disclosure as a PAGE, not a link to raw JSON. The
16
+ // regulation asks for a plain-language description of the main ranking
17
+ // parameters; `GET /search/api/v1/ranking` is the data behind it, and
18
+ // handing a visitor that JSON would satisfy nobody's reading of it. The
19
+ // route is `/ranking-disclosure`, not `/ranking`, so the SPA path cannot
20
+ // be mistaken — by a person or by `stapel/no-string-paths` — for the API
21
+ // operation of the same name.
22
+ id: "search.ranking",
23
+ labelKey: "search.ranking.title",
24
+ icon: "OrderedListOutlined",
25
+ route: { path: "/ranking-disclosure" },
26
+ component: { export: "RankingDisclosurePane", subpath: "default" },
27
+ placement: { level: "top" },
28
+ menuVisibleDefault: false,
29
+ requiresAuth: false,
30
+ surface: "public",
31
+ order: 11,
32
+ },
33
+ ];
34
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/nav/manifest.ts"],"names":[],"mappings":"AAoBA,MAAM,CAAC,MAAM,UAAU,GAAwB;IAC7C;QACE,EAAE,EAAE,gBAAgB;QACpB,QAAQ,EAAE,sBAAsB;QAChC,IAAI,EAAE,gBAAgB;QACtB,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;QACrB,SAAS,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE;QACvD,SAAS,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE;QAC3B,kBAAkB,EAAE,KAAK;QACzB,YAAY,EAAE,KAAK;QACnB,OAAO,EAAE,QAAQ;QACjB,KAAK,EAAE,EAAE;KACV;IACD;QACE,mEAAmE;QACnE,uEAAuE;QACvE,sEAAsE;QACtE,wEAAwE;QACxE,yEAAyE;QACzE,yEAAyE;QACzE,8BAA8B;QAC9B,EAAE,EAAE,gBAAgB;QACpB,QAAQ,EAAE,sBAAsB;QAChC,IAAI,EAAE,qBAAqB;QAC3B,KAAK,EAAE,EAAE,IAAI,EAAE,qBAAqB,EAAE;QACtC,SAAS,EAAE,EAAE,MAAM,EAAE,uBAAuB,EAAE,OAAO,EAAE,SAAS,EAAE;QAClE,SAAS,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE;QAC3B,kBAAkB,EAAE,KAAK;QACzB,YAAY,EAAE,KAAK;QACnB,OAAO,EAAE,QAAQ;QACjB,KAAK,EAAE,EAAE;KACV;CACF,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { SearchParamsAdapter } from "../headless/SearchStateProvider.js";
2
+ /**
3
+ * react-router's `useSearchParams()` as a {@link SearchParamsAdapter}.
4
+ *
5
+ * `replace` is forwarded rather than dropped: a facet click PUSHES, so Back
6
+ * removes exactly that filter (the spec's §4.2 acceptance), while typing in
7
+ * the search box replaces so one history entry per keystroke never happens.
8
+ */
9
+ export declare function useRouterSearchParams(): SearchParamsAdapter;
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/router/index.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AAE9E;;;;;;GAMG;AACH,wBAAgB,qBAAqB,IAAI,mBAAmB,CAW3D"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * `@stapel/search-react/router` — the react-router v7 binding for the URL
3
+ * state, and nothing else.
4
+ *
5
+ * A SEPARATE entry point on purpose. URL state is a router concern, and the
6
+ * pair does not own the host's router: a Next.js app, a hash router, or a
7
+ * server render that reads `new URL(request.url).searchParams` all satisfy
8
+ * `SearchParamsAdapter` without this file. Keeping the binding here means the
9
+ * main entry pulls no router at all (size-limit proves it), and a host that
10
+ * uses react-router gets the call it cannot make wrong:
11
+ *
12
+ * ```tsx
13
+ * <SearchStateProvider adapter={useRouterSearchParams()} defaultType="listing">
14
+ * ```
15
+ */
16
+ import { useMemo } from "react";
17
+ import { useSearchParams } from "react-router";
18
+ /**
19
+ * react-router's `useSearchParams()` as a {@link SearchParamsAdapter}.
20
+ *
21
+ * `replace` is forwarded rather than dropped: a facet click PUSHES, so Back
22
+ * removes exactly that filter (the spec's §4.2 acceptance), while typing in
23
+ * the search box replaces so one history entry per keystroke never happens.
24
+ */
25
+ export function useRouterSearchParams() {
26
+ const [params, setParams] = useSearchParams();
27
+ return useMemo(() => ({
28
+ params,
29
+ setParams: (next, options) => {
30
+ setParams(next, { replace: options?.replace ?? false });
31
+ },
32
+ }), [params, setParams]);
33
+ }
34
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/router/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAG/C;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB;IACnC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,eAAe,EAAE,CAAC;IAC9C,OAAO,OAAO,CACZ,GAAG,EAAE,CAAC,CAAC;QACL,MAAM;QACN,SAAS,EAAE,CAAC,IAAqB,EAAE,OAAwC,EAAE,EAAE;YAC7E,SAAS,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,CAAC,CAAC;QAC1D,CAAC;KACF,CAAC,EACF,CAAC,MAAM,EAAE,SAAS,CAAC,CACpB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * `degraded[]` — the backend declaring, per query, what the configured engine
3
+ * could not do. The UI's job is to say it, not to smooth it over: a result
4
+ * page that quietly drops "typo tolerance is off" is a page that looks like a
5
+ * complete answer and is not one.
6
+ *
7
+ * The literals are produced in `stapel-search/services.py::_degradations` plus
8
+ * whatever the backend and the facet counter contribute; the concatenation is
9
+ * NOT de-duplicated upstream, so `"typo_tolerance"` can genuinely arrive
10
+ * twice. {@link parseDegradations} de-duplicates by the raw literal.
11
+ *
12
+ * An unrecognised literal is kept, not dropped — the honest failure mode for
13
+ * "the backend degraded in a way this build predates" is a generic sentence
14
+ * with the literal beside it, not silence.
15
+ */
16
+ import type { SearchDegradation, SearchDegradationKind } from "../api/types.js";
17
+ /** i18n key per kind (`search.degraded.*`). */
18
+ export declare function degradationMessageKey(kind: SearchDegradationKind): string;
19
+ /** Parse and de-duplicate the envelope's `degraded[]`. */
20
+ export declare function parseDegradations(degraded: readonly string[] | undefined): readonly SearchDegradation[];
21
+ /**
22
+ * Does this set of degradations mean the RESULT COUNT is an estimate?
23
+ *
24
+ * Two independent signals say so and a skin must not have to know which:
25
+ * the envelope's own `exact_total: false`, and the `"exact_total"`
26
+ * degradation. They agree in practice; treating either as decisive is what
27
+ * keeps "≈1 200" from ever being rendered as "1 200".
28
+ */
29
+ export declare function countIsEstimate(exactTotal: boolean, degradations: readonly SearchDegradation[]): boolean;
30
+ //# sourceMappingURL=degradations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"degradations.d.ts","sourceRoot":"","sources":["../../src/state/degradations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAYhF,+CAA+C;AAC/C,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,qBAAqB,GAAG,MAAM,CAEzE;AAED,0DAA0D;AAC1D,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,GACtC,SAAS,iBAAiB,EAAE,CAwB9B;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,OAAO,EACnB,YAAY,EAAE,SAAS,iBAAiB,EAAE,GACzC,OAAO,CAET"}
@@ -0,0 +1,52 @@
1
+ const SCORER_PREFIX = "scorer:";
2
+ const KNOWN = {
3
+ typo_tolerance: "typo_tolerance",
4
+ phrase_synonyms: "phrase_synonyms",
5
+ exact_total: "exact_total",
6
+ exact_facet_counts: "exact_facet_counts",
7
+ category_rollup: "category_rollup",
8
+ };
9
+ /** i18n key per kind (`search.degraded.*`). */
10
+ export function degradationMessageKey(kind) {
11
+ return `search.degraded.${kind}`;
12
+ }
13
+ /** Parse and de-duplicate the envelope's `degraded[]`. */
14
+ export function parseDegradations(degraded) {
15
+ if (degraded === undefined)
16
+ return [];
17
+ const seen = new Set();
18
+ const out = [];
19
+ for (const raw of degraded) {
20
+ if (seen.has(raw))
21
+ continue;
22
+ seen.add(raw);
23
+ if (raw.startsWith(SCORER_PREFIX)) {
24
+ out.push({
25
+ kind: "scorer",
26
+ raw,
27
+ scorer: raw.slice(SCORER_PREFIX.length),
28
+ messageKey: degradationMessageKey("scorer"),
29
+ });
30
+ continue;
31
+ }
32
+ const kind = KNOWN[raw];
33
+ out.push({
34
+ kind: kind ?? "unknown",
35
+ raw,
36
+ messageKey: degradationMessageKey(kind ?? "unknown"),
37
+ });
38
+ }
39
+ return out;
40
+ }
41
+ /**
42
+ * Does this set of degradations mean the RESULT COUNT is an estimate?
43
+ *
44
+ * Two independent signals say so and a skin must not have to know which:
45
+ * the envelope's own `exact_total: false`, and the `"exact_total"`
46
+ * degradation. They agree in practice; treating either as decisive is what
47
+ * keeps "≈1 200" from ever being rendered as "1 200".
48
+ */
49
+ export function countIsEstimate(exactTotal, degradations) {
50
+ return !exactTotal || degradations.some((d) => d.kind === "exact_total");
51
+ }
52
+ //# sourceMappingURL=degradations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"degradations.js","sourceRoot":"","sources":["../../src/state/degradations.ts"],"names":[],"mappings":"AAiBA,MAAM,aAAa,GAAG,SAAS,CAAC;AAEhC,MAAM,KAAK,GAAoD;IAC7D,cAAc,EAAE,gBAAgB;IAChC,eAAe,EAAE,iBAAiB;IAClC,WAAW,EAAE,aAAa;IAC1B,kBAAkB,EAAE,oBAAoB;IACxC,eAAe,EAAE,iBAAiB;CACnC,CAAC;AAEF,+CAA+C;AAC/C,MAAM,UAAU,qBAAqB,CAAC,IAA2B;IAC/D,OAAO,mBAAmB,IAAI,EAAE,CAAC;AACnC,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,iBAAiB,CAC/B,QAAuC;IAEvC,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IACtC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,GAAG,GAAwB,EAAE,CAAC;IACpC,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAS;QAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACd,IAAI,GAAG,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;YAClC,GAAG,CAAC,IAAI,CAAC;gBACP,IAAI,EAAE,QAAQ;gBACd,GAAG;gBACH,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC;gBACvC,UAAU,EAAE,qBAAqB,CAAC,QAAQ,CAAC;aAC5C,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;QACxB,GAAG,CAAC,IAAI,CAAC;YACP,IAAI,EAAE,IAAI,IAAI,SAAS;YACvB,GAAG;YACH,UAAU,EAAE,qBAAqB,CAAC,IAAI,IAAI,SAAS,CAAC;SACrD,CAAC,CAAC;IACL,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAC7B,UAAmB,EACnB,YAA0C;IAE1C,OAAO,CAAC,UAAU,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,aAAa,CAAC,CAAC;AAC3E,CAAC"}
@@ -0,0 +1,68 @@
1
+ import type { FeatureDef } from "@stapel/attributes-react";
2
+ import type { FacetMeta, SearchQueryState } from "../api/types.js";
3
+ /** One value of one facet. */
4
+ export interface FacetOption {
5
+ readonly value: string;
6
+ /**
7
+ * How many candidates carry this value with the slug's own filter removed —
8
+ * or `null` when the slug was not counted (skipped, or facets off).
9
+ */
10
+ readonly count: number | null;
11
+ /** Resolved through the category schema; the raw value when there is none. */
12
+ readonly label: string;
13
+ readonly selected: boolean;
14
+ }
15
+ /** One facet slug, with its options. */
16
+ export interface FacetGroup {
17
+ readonly slug: string;
18
+ /** The feature's display name (translated when it is a key), else the slug. */
19
+ readonly label: string;
20
+ /** The category-schema entry behind the slug, when the host supplied one. */
21
+ readonly feature: FeatureDef | undefined;
22
+ /** `false` when the server skipped this slug — counts are `null`. */
23
+ readonly counted: boolean;
24
+ readonly options: readonly FacetOption[];
25
+ /** The values currently chosen for this slug (URL state, not the response). */
26
+ readonly selected: readonly string[];
27
+ }
28
+ export interface BuildFacetGroupsInput {
29
+ /** The envelope's `facets`: `{slug: {value: count}}`. */
30
+ readonly facets: Readonly<Record<string, Readonly<Record<string, number>>>>;
31
+ readonly meta: FacetMeta;
32
+ /** Current URL state — the source of what is selected. */
33
+ readonly state: SearchQueryState;
34
+ /** The category's feature schema, for labels and option order. */
35
+ readonly categoryFeatures?: readonly FeatureDef[];
36
+ /** Translator for label keys. */
37
+ readonly t?: (key: string) => string;
38
+ /** BCP-47 tag, forwarded to `formatFeatureValue` for `date` options. */
39
+ readonly locale?: string;
40
+ }
41
+ /**
42
+ * A facet value as a person reads it, through
43
+ * `@stapel/attributes-react`'s `formatFeatureValue` — the same formatter the
44
+ * card and the spec table use, so a value cannot read one way in the filter
45
+ * and another way in the result.
46
+ *
47
+ * The wire carries facet values as STRINGS (they are index terms), so the raw
48
+ * value is coerced back into the shape the type's formatter expects before
49
+ * being handed over. `select` is a list even for one value
50
+ * (`types/select/dto.py`), `date` is a Unix timestamp integer, numbers are
51
+ * numbers. A type the formatter cannot read gives back the raw value — never
52
+ * an empty label, which would leave an unnameable checkbox on screen.
53
+ */
54
+ export declare function facetOptionLabel(feature: FeatureDef | undefined, value: string, options?: {
55
+ readonly t?: (key: string) => string;
56
+ readonly locale?: string;
57
+ }): string;
58
+ /**
59
+ * Fold the response's counts, the URL's selections and the category schema
60
+ * into the panel's groups.
61
+ *
62
+ * Groups appear for every counted slug, every skipped slug, and every slug the
63
+ * person has filtered on — the last one matters: a filter whose slug fell out
64
+ * of the plan must stay visible, or it becomes a constraint with no control to
65
+ * remove it.
66
+ */
67
+ export declare function buildFacetGroups(input: BuildFacetGroupsInput): readonly FacetGroup[];
68
+ //# sourceMappingURL=facets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"facets.d.ts","sourceRoot":"","sources":["../../src/state/facets.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEnE,8BAA8B;AAC9B,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED,wCAAwC;AACxC,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+EAA+E;IAC/E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,EAAE,UAAU,GAAG,SAAS,CAAC;IACzC,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAC;IACzC,+EAA+E;IAC/E,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAED,MAAM,WAAW,qBAAqB;IACpC,yDAAyD;IACzD,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5E,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,0DAA0D;IAC1D,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;IACjC,kEAAkE;IAClE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IAClD,iCAAiC;IACjC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IACrC,wEAAwE;IACxE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAgCD;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,UAAU,GAAG,SAAS,EAC/B,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3E,MAAM,CAkCR;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,SAAS,UAAU,EAAE,CA2DpF"}