@stapel/search-react 0.10.0 → 0.12.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 (130) hide show
  1. package/CHANGELOG.md +160 -0
  2. package/MODULE.md +111 -0
  3. package/README.md +42 -0
  4. package/dist/api/generated/schema.d.ts +76 -6
  5. package/dist/api/generated/schema.d.ts.map +1 -1
  6. package/dist/api/searchApi.d.ts +13 -6
  7. package/dist/api/searchApi.d.ts.map +1 -1
  8. package/dist/api/searchApi.js.map +1 -1
  9. package/dist/api/types.d.ts +107 -1
  10. package/dist/api/types.d.ts.map +1 -1
  11. package/dist/api/types.js +29 -0
  12. package/dist/api/types.js.map +1 -1
  13. package/dist/default/DegradationNotice.d.ts +22 -13
  14. package/dist/default/DegradationNotice.d.ts.map +1 -1
  15. package/dist/default/DegradationNotice.js +7 -8
  16. package/dist/default/DegradationNotice.js.map +1 -1
  17. package/dist/default/FacetPanelPane.d.ts +13 -2
  18. package/dist/default/FacetPanelPane.d.ts.map +1 -1
  19. package/dist/default/FacetPanelPane.js +29 -16
  20. package/dist/default/FacetPanelPane.js.map +1 -1
  21. package/dist/default/FilterChips.d.ts +88 -3
  22. package/dist/default/FilterChips.d.ts.map +1 -1
  23. package/dist/default/FilterChips.js +166 -13
  24. package/dist/default/FilterChips.js.map +1 -1
  25. package/dist/default/LocationSummaryLine.d.ts.map +1 -1
  26. package/dist/default/LocationSummaryLine.js +1 -1
  27. package/dist/default/LocationSummaryLine.js.map +1 -1
  28. package/dist/default/RangeFilterRow.d.ts.map +1 -1
  29. package/dist/default/RangeFilterRow.js +49 -5
  30. package/dist/default/RangeFilterRow.js.map +1 -1
  31. package/dist/default/SearchBox.d.ts.map +1 -1
  32. package/dist/default/SearchBox.js +74 -4
  33. package/dist/default/SearchBox.js.map +1 -1
  34. package/dist/default/SearchPage.d.ts +29 -1
  35. package/dist/default/SearchPage.d.ts.map +1 -1
  36. package/dist/default/SearchPage.js +25 -10
  37. package/dist/default/SearchPage.js.map +1 -1
  38. package/dist/default/SearchResultsPane.d.ts +22 -0
  39. package/dist/default/SearchResultsPane.d.ts.map +1 -1
  40. package/dist/default/SearchResultsPane.js +2 -2
  41. package/dist/default/SearchResultsPane.js.map +1 -1
  42. package/dist/default/SortSelect.d.ts +23 -0
  43. package/dist/default/SortSelect.d.ts.map +1 -1
  44. package/dist/default/SortSelect.js +29 -9
  45. package/dist/default/SortSelect.js.map +1 -1
  46. package/dist/default/index.d.ts +2 -2
  47. package/dist/default/index.d.ts.map +1 -1
  48. package/dist/default/index.js +1 -1
  49. package/dist/default/index.js.map +1 -1
  50. package/dist/headless/FacetPanel.d.ts +26 -0
  51. package/dist/headless/FacetPanel.d.ts.map +1 -1
  52. package/dist/headless/FacetPanel.js +16 -1
  53. package/dist/headless/FacetPanel.js.map +1 -1
  54. package/dist/headless/useFacetLabels.d.ts +39 -0
  55. package/dist/headless/useFacetLabels.d.ts.map +1 -0
  56. package/dist/headless/useFacetLabels.js +129 -0
  57. package/dist/headless/useFacetLabels.js.map +1 -0
  58. package/dist/headless/useSearchBox.d.ts +71 -0
  59. package/dist/headless/useSearchBox.d.ts.map +1 -1
  60. package/dist/headless/useSearchBox.js +67 -2
  61. package/dist/headless/useSearchBox.js.map +1 -1
  62. package/dist/i18n/es.d.ts.map +1 -1
  63. package/dist/i18n/es.js +8 -0
  64. package/dist/i18n/es.js.map +1 -1
  65. package/dist/i18n/keys.d.ts +48 -0
  66. package/dist/i18n/keys.d.ts.map +1 -1
  67. package/dist/i18n/keys.js +58 -0
  68. package/dist/i18n/keys.js.map +1 -1
  69. package/dist/i18n/ru.d.ts.map +1 -1
  70. package/dist/i18n/ru.js +10 -0
  71. package/dist/i18n/ru.js.map +1 -1
  72. package/dist/index.d.ts +9 -7
  73. package/dist/index.d.ts.map +1 -1
  74. package/dist/index.js +6 -5
  75. package/dist/index.js.map +1 -1
  76. package/dist/model/queries.d.ts +6 -5
  77. package/dist/model/queries.d.ts.map +1 -1
  78. package/dist/model/queries.js +4 -3
  79. package/dist/model/queries.js.map +1 -1
  80. package/dist/model/queryKeys.d.ts +12 -0
  81. package/dist/model/queryKeys.d.ts.map +1 -1
  82. package/dist/model/queryKeys.js +7 -0
  83. package/dist/model/queryKeys.js.map +1 -1
  84. package/dist/nav/manifest.d.ts +21 -0
  85. package/dist/nav/manifest.d.ts.map +1 -1
  86. package/dist/nav/manifest.js +3 -2
  87. package/dist/nav/manifest.js.map +1 -1
  88. package/dist/state/degradations.d.ts +32 -0
  89. package/dist/state/degradations.d.ts.map +1 -1
  90. package/dist/state/degradations.js +33 -0
  91. package/dist/state/degradations.js.map +1 -1
  92. package/dist/state/facets.d.ts +64 -0
  93. package/dist/state/facets.d.ts.map +1 -1
  94. package/dist/state/facets.js +134 -9
  95. package/dist/state/facets.js.map +1 -1
  96. package/dist/state/ranges.d.ts +45 -3
  97. package/dist/state/ranges.d.ts.map +1 -1
  98. package/dist/state/ranges.js +55 -14
  99. package/dist/state/ranges.js.map +1 -1
  100. package/llms.txt +2 -2
  101. package/manifest.json +27 -2
  102. package/nav-manifest.json +4 -3
  103. package/package.json +10 -10
  104. package/src/analytics/generated/events.json +1 -1
  105. package/src/api/generated/schema.ts +76 -6
  106. package/src/api/searchApi.ts +13 -6
  107. package/src/api/types.ts +115 -1
  108. package/src/default/DegradationNotice.tsx +30 -20
  109. package/src/default/FacetPanelPane.tsx +55 -25
  110. package/src/default/FilterChips.tsx +290 -38
  111. package/src/default/LocationSummaryLine.tsx +6 -1
  112. package/src/default/RangeFilterRow.tsx +61 -3
  113. package/src/default/SearchBox.tsx +134 -6
  114. package/src/default/SearchPage.tsx +79 -5
  115. package/src/default/SearchResultsPane.tsx +48 -11
  116. package/src/default/SortSelect.tsx +70 -9
  117. package/src/default/index.ts +10 -2
  118. package/src/headless/FacetPanel.tsx +44 -1
  119. package/src/headless/useFacetLabels.ts +172 -0
  120. package/src/headless/useSearchBox.ts +132 -5
  121. package/src/i18n/es.ts +9 -0
  122. package/src/i18n/keys.ts +60 -0
  123. package/src/i18n/ru.ts +11 -0
  124. package/src/index.ts +28 -4
  125. package/src/model/queries.ts +6 -5
  126. package/src/model/queryKeys.ts +23 -0
  127. package/src/nav/manifest.ts +24 -2
  128. package/src/state/degradations.ts +56 -0
  129. package/src/state/facets.ts +181 -9
  130. package/src/state/ranges.ts +85 -15
@@ -8,25 +8,107 @@
8
8
  * they would have if you swapped to them. A panel that greys the siblings
9
9
  * out has silently converted a drill-down facet into a naive one, and the
10
10
  * e2e leg in the spec (§7.2) exists to catch exactly that.
11
- * 2. **The server does not send option LABELS** (`facets.py` returns
12
- * `{value: count}` and nothing else). The labels are in the category's
13
- * feature schema, which is why {@link buildFacetGroups} takes
14
- * `categoryFeatures` — the second slot-seam of the pair, filled by the
15
- * container from `categories-react` (spec §6.2 item 2). Without it the
16
- * panel still works and shows raw values; it does not invent labels.
11
+ * 2. **Option LABELS have one stated order: ANSWER, then schema, then the
12
+ * raw value** with a host resolver between the last two, applied by
13
+ * `useHostFacetLabels` on what this module could not name.
14
+ *
15
+ * The answer leads because it is the only source that always exists and
16
+ * the only one that has RESOLVED anything: `categoryFeatures` is an
17
+ * optional slot a live classified board never filled, and even where it is
18
+ * filled a `ref_select` config carries a pointer to a vocabulary and no
19
+ * option table at all — so the schema cannot name `apple` or `chernyy` no
20
+ * matter who threads it through. `facet_labels` (stapel-search 0.4.0+,
21
+ * vocabulary-backed from 0.6.0) is the server reading that vocabulary
22
+ * against the snapshot the documents were written with, and answering in
23
+ * the request's own language. The schema is the floor under it, for a
24
+ * server too old to send captions and for a slug the answer omits.
25
+ *
26
+ * Neither invents a label: a value nobody names renders as itself. A page
27
+ * against a pre-0.4.0 server therefore behaves exactly as it did — the key
28
+ * is absent, every option falls through to the schema, and nothing
29
+ * crashes on the missing key.
17
30
  *
18
31
  * A slug the server SKIPPED (`facet_meta.skipped`, dropped at
19
32
  * `MAX_FACET_FIELDS`) is not counted at all. Its options carry `count: null`,
20
33
  * never `0` — "we did not count this" and "there are none" are different
21
34
  * sentences and the honest one has to survive to the screen.
35
+ *
36
+ * 3. **Not every counted slug is a FILTER.** The plan is built from the leaf
37
+ * category's feature defs and the counter counts whatever is indexed, so a
38
+ * live classified deployment answers with `imei: {"355971829187494": 1}`
39
+ * and `video_file_url: {}` beside its brand and its condition. Neither is
40
+ * something a person narrows by — one is unique per document, the other is
41
+ * a URL — and on a 390px chip row a chip offering one IMEI with a count of
42
+ * one pushes the chips that DO narrow off the screen. Which slugs can be
43
+ * chipped is therefore decided from the category's own feature defs, by
44
+ * value TYPE (see {@link FACETABLE_FEATURE_TYPES}), not by what came back.
45
+ */
46
+ import { VOCABULARY_BACKED_TYPES, featureConfig, featureName, featureType, formatFeatureValue, } from "@stapel/attributes-react";
47
+ /**
48
+ * Value types whose values are a BOUNDED OPTION SET — the only kind of
49
+ * feature a person can be offered as a filter.
50
+ *
51
+ * The list is not invented here: the ref/vocabulary-backed half is
52
+ * `@stapel/attributes-react`'s own `VOCABULARY_BACKED_TYPES` (the one place
53
+ * that knows which types are drawn from a vocabulary), and the rest are the
54
+ * select family plus `bool`. Everything else a category can declare — a
55
+ * `string`, an `int`, a `date`, a `hex_color`, a `group` — enumerates as many
56
+ * terms as there are documents, which is a list, not a choice.
57
+ *
58
+ * `int`/`float`/`convertible_unit` are absent here and present in
59
+ * `RANGE_FEATURE_TYPES` (`state/ranges.ts`): a number is narrowed with two
60
+ * bounds, not with a checkbox per value. They are not dropped from the panel,
61
+ * they are drawn by the other half of the model.
22
62
  */
23
- import { featureConfig, featureName, featureType, formatFeatureValue } from "@stapel/attributes-react";
63
+ export const FACETABLE_FEATURE_TYPES = [
64
+ "bool",
65
+ "hierarchical_select",
66
+ ...VOCABULARY_BACKED_TYPES,
67
+ "select",
68
+ ];
69
+ /**
70
+ * Can a person filter by the slug this feature def describes?
71
+ *
72
+ * **A missing feature def is not a "no".** `categoryFeatures` is an OPTIONAL
73
+ * slot and a whole deployment can run without it; a feature can also be
74
+ * retired between the write that indexed a value and the read that counts it.
75
+ * Answering "not facetable" for an absent def would blank the entire chip row
76
+ * for every host that never threaded the schema through — the same empty row
77
+ * this rule exists to fix, arrived at from the other side. So the schema can
78
+ * only ever REMOVE a chip it names and disowns; silence removes nothing.
79
+ *
80
+ * The same reasoning covers a def with no `config.type` at all: an untyped
81
+ * feature is a def that says nothing, and nothing is not a verdict.
82
+ */
83
+ export function isFacetableFeature(feature) {
84
+ if (feature === undefined)
85
+ return true;
86
+ const type = featureType(feature);
87
+ if (type === undefined)
88
+ return true;
89
+ return FACETABLE_FEATURE_TYPES.includes(type);
90
+ }
24
91
  function translate(t, key) {
25
92
  if (t === undefined)
26
93
  return key;
27
94
  const resolved = t(key);
28
95
  return resolved.length > 0 ? resolved : key;
29
96
  }
97
+ /**
98
+ * The caption the ANSWER carries for one option, or `undefined`.
99
+ *
100
+ * `translatable` is the server saying whether `values` holds translation
101
+ * KEYS or literal captions, and it has to be said rather than sniffed:
102
+ * `b.apple` and a rendered caption are both strings, and guessing wrong prints either a
103
+ * dotted key or an untranslated word at a buyer.
104
+ */
105
+ function serverLabel(labels, slug, value, t) {
106
+ const entry = labels?.[slug];
107
+ const caption = entry?.values[value];
108
+ if (caption === undefined || caption.length === 0)
109
+ return undefined;
110
+ return entry?.translatable === true ? translate(t, caption) : caption;
111
+ }
30
112
  /**
31
113
  * The declared option ORDER for a closed set, if the schema declares one.
32
114
  *
@@ -54,6 +136,25 @@ function declaredOptionValues(feature) {
54
136
  }
55
137
  return out;
56
138
  }
139
+ /**
140
+ * One option's caption: the ANSWER, then the schema, then the raw value.
141
+ *
142
+ * The answer leads because it is the source that always exists and the only
143
+ * one that can name a vocabulary-backed value at all: a `ref_select`'s config
144
+ * is a POINTER (`optionsRef`), so a host who threaded the whole schema through
145
+ * still has nothing to print for `apple`. The server read that vocabulary
146
+ * against the snapshot the documents carry and answered in the request's own
147
+ * language, which is a strictly better-informed caption than the option table
148
+ * the schema may or may not hold.
149
+ *
150
+ * The schema is the floor: a server too old to send `facet_labels`, a slug the
151
+ * answer omits, a value the vocabulary has since dropped. Below both, the raw
152
+ * value — never a blank, and never a guess.
153
+ */
154
+ function resolveLabel(input, feature, slug, value, labelOptions) {
155
+ return (serverLabel(input.facetLabels, slug, value, input.t) ??
156
+ facetOptionLabel(feature, value, labelOptions));
157
+ }
57
158
  /**
58
159
  * A facet value as a person reads it, through
59
160
  * `@stapel/attributes-react`'s `formatFeatureValue` — the same formatter the
@@ -106,6 +207,13 @@ export function facetOptionLabel(feature, value, options) {
106
207
  * person has filtered on — the last one matters: a filter whose slug fell out
107
208
  * of the plan must stay visible, or it becomes a constraint with no control to
108
209
  * remove it.
210
+ *
211
+ * A slug the category schema names and types as something no one can choose
212
+ * from (see {@link isFacetableFeature}) produces no group at all — not an
213
+ * empty one, because an empty group is still a heading in the panel and still
214
+ * a chip in the row. The APPLIED-filter clause outranks the type rule: a
215
+ * constraint the URL carries always gets its control back, whatever the schema
216
+ * now says about it, or a person is left holding a filter they cannot clear.
109
217
  */
110
218
  export function buildFacetGroups(input) {
111
219
  const bySlug = new Map();
@@ -122,6 +230,11 @@ export function buildFacetGroups(input) {
122
230
  if (seen.has(slug))
123
231
  continue;
124
232
  seen.add(slug);
233
+ // Applied first, type second — in that order, so an `imei` somebody
234
+ // somehow got into a link keeps the control that removes it.
235
+ const applied = (input.state.filters[slug] ?? []).length > 0;
236
+ if (!applied && !isFacetableFeature(bySlug.get(slug)))
237
+ continue;
125
238
  slugs.push(slug);
126
239
  }
127
240
  return slugs.map((slug) => {
@@ -129,7 +242,14 @@ export function buildFacetGroups(input) {
129
242
  const counts = input.facets[slug] ?? {};
130
243
  const counted = !skipped.has(slug) && slug in input.facets;
131
244
  const selected = input.state.filters[slug] ?? [];
132
- const declared = declaredOptionValues(feature);
245
+ // The authored option order, from whichever copy of the category config
246
+ // this page has. An authored list reshuffled by count is a size chart
247
+ // that moves on every click, and until 0.11 a host that passed no schema
248
+ // got exactly that.
249
+ const fromSchema = declaredOptionValues(feature);
250
+ const declared = fromSchema.length > 0
251
+ ? fromSchema
252
+ : Object.keys(input.facetLabels?.[slug]?.values ?? {});
133
253
  const values = [];
134
254
  const push = (value) => {
135
255
  if (!values.includes(value))
@@ -162,7 +282,12 @@ export function buildFacetGroups(input) {
162
282
  options: values.map((value) => ({
163
283
  value,
164
284
  count: counted ? (counts[value] ?? 0) : null,
165
- label: facetOptionLabel(feature, value, labelOptions),
285
+ // The host's schema first when it actually resolves the value, the
286
+ // answer's caption when it does not (or when there is no schema at
287
+ // all), the raw value when neither knows. `facetOptionLabel` returns
288
+ // the value unchanged for an option it cannot name, which is what
289
+ // makes "did it resolve?" answerable without a second lookup.
290
+ label: resolveLabel(input, feature, slug, value, labelOptions),
166
291
  selected: selected.includes(value),
167
292
  })),
168
293
  };
@@ -1 +1 @@
1
- {"version":3,"file":"facets.js","sourceRoot":"","sources":["../../src/state/facets.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"facets.js","sourceRoot":"","sources":["../../src/state/facets.ts"],"names":[],"mappings":"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"}
@@ -25,19 +25,61 @@ export interface RangeGroup {
25
25
  readonly step: number | undefined;
26
26
  /** Whether the URL currently constrains this slug. */
27
27
  readonly active: boolean;
28
+ /**
29
+ * `true` when the axis is a CORE document column the server declared in
30
+ * `facet_meta.core_ranges` rather than a category feature. Price is the
31
+ * shipped one. A core axis is drawn first and is never absent because a
32
+ * category forgot to declare an attribute for it.
33
+ */
34
+ readonly core: boolean;
35
+ /**
36
+ * ISO 4217 code when the row is money, so the control can read as money
37
+ * instead of as a bare integer. Only ever set on a core axis: an attribute
38
+ * carries a `postfix`, not a currency.
39
+ */
40
+ readonly currency: string | undefined;
28
41
  }
42
+ /**
43
+ * i18n key for a core axis's own name.
44
+ *
45
+ * A core axis has no FeatureDef and therefore no `name` to translate. The
46
+ * label is this package's, because the axis is this package's.
47
+ */
48
+ export declare function coreRangeLabelKey(slug: string): string;
29
49
  export interface BuildRangeGroupsInput {
30
50
  readonly state: SearchQueryState;
31
51
  readonly categoryFeatures?: readonly FeatureDef[];
52
+ /**
53
+ * `facet_meta.core_ranges` — range slugs that address a column of the
54
+ * document rather than an attribute (stapel-search 0.4.0+).
55
+ *
56
+ * It comes from the ANSWER on purpose. A live classified board offered
57
+ * seven numeric ranges — parcel weight, length, height, width, packing
58
+ * quantity, minimum-order quantity, battery condition — and no price,
59
+ * because a row was only ever drawn for a category feature and price is a
60
+ * column of the listing. Hardcoding `"price"` here would have fixed that
61
+ * board and broken the next one, where `r.price` still answers zero
62
+ * because the server predates the axis. So the server says which axes it
63
+ * can actually serve, and this list is empty against an older one.
64
+ */
65
+ readonly coreRanges?: readonly string[];
66
+ /** ISO 4217 code for the money axes, when the surface knows one. */
67
+ readonly currency?: string;
32
68
  /** Translator for label keys (the schema's `name` is often one). */
33
69
  readonly t?: (key: string) => string;
34
70
  }
35
71
  /** Is this feature one a numeric range row is drawn for? */
36
72
  export declare function isRangeFeature(feature: FeatureDef): boolean;
37
73
  /**
38
- * The range rows for the current search: every numeric feature of the
39
- * category, in the schema's own order, plus any slug the URL constrains that
40
- * the schema does not explain.
74
+ * The range rows for the current search, in the order they should be read:
75
+ * the CORE axes the answer declares, then every numeric feature of the
76
+ * category in the schema's own order, then any slug the URL constrains that
77
+ * neither explains.
78
+ *
79
+ * Core first is not cosmetic. On the board this was measured against, the
80
+ * seven numeric attributes a phone category happens to declare are all
81
+ * shipping and wholesale inputs; the one number a phone buyer narrows by is
82
+ * the price, and it belongs above them.
41
83
  */
42
84
  export declare function buildRangeGroups(input: BuildRangeGroupsInput): readonly RangeGroup[];
43
85
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"ranges.d.ts","sourceRoot":"","sources":["../../src/state/ranges.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAErE;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAIhD,CAAC;AAEF,8CAA8C;AAC9C,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+EAA+E;IAC/E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,oEAAoE;IACpE,QAAQ,CAAC,OAAO,EAAE,UAAU,GAAG,SAAS,CAAC;IACzC;iFAC6E;IAC7E,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,8DAA8D;IAC9D,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,0EAA0E;IAC1E,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,4EAA4E;IAC5E,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,sDAAsD;IACtD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;IACjC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IAClD,oEAAoE;IACpE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;CACtC;AAgBD,4DAA4D;AAC5D,wBAAgB,cAAc,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAG3D;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,qBAAqB,GAC3B,SAAS,UAAU,EAAE,CA8BvB;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAMzD"}
1
+ {"version":3,"file":"ranges.d.ts","sourceRoot":"","sources":["../../src/state/ranges.ts"],"names":[],"mappings":"AAsCA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAErE;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAIhD,CAAC;AAEF,8CAA8C;AAC9C,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+EAA+E;IAC/E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,oEAAoE;IACpE,QAAQ,CAAC,OAAO,EAAE,UAAU,GAAG,SAAS,CAAC;IACzC;iFAC6E;IAC7E,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,8DAA8D;IAC9D,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,0EAA0E;IAC1E,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,4EAA4E;IAC5E,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,sDAAsD;IACtD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;CACvC;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;IACjC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IAClD;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,oEAAoE;IACpE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,oEAAoE;IACpE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;CACtC;AAgBD,4DAA4D;AAC5D,wBAAgB,cAAc,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAG3D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,qBAAqB,GAC3B,SAAS,UAAU,EAAE,CA6CvB;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAMzD"}
@@ -6,13 +6,23 @@
6
6
  * the panel only knew how to draw checkboxes. This module is the missing half:
7
7
  * which slugs a range row exists for, and what the row is called.
8
8
  *
9
- * ── Where a range row comes from, and why it is not the response ───────────
9
+ * ── Where a range row comes from ──────────────────────────────────────────
10
10
  *
11
- * A facet answer (`facets: {slug: {value: count}}`) enumerates DISCRETE values;
12
- * a range is not enumerable and the server never sends one. So the rows come
13
- * from the CATEGORY SCHEMA the same `categoryFeatures` slot that gives the
14
- * checkboxes their labels — filtered to the numeric value types, plus every
15
- * slug the URL already carries a range for.
11
+ * A facet answer (`facets: {slug: {value: count}}`) enumerates DISCRETE
12
+ * values; a range is not enumerable and no bucket is ever sent for one. So
13
+ * the rows come from two places:
14
+ *
15
+ * - the CATEGORY SCHEMA — the same `categoryFeatures` slot that gives the
16
+ * checkboxes their labels — filtered to the numeric value types;
17
+ * - the ANSWER's `facet_meta.core_ranges` (stapel-search 0.4.0+), which
18
+ * names the range slugs addressing a COLUMN of the document rather than
19
+ * an attribute. `price` is the shipped one, and it is why this module
20
+ * exists at all: a live classified board offered seven numeric ranges,
21
+ * every one of them a shipping or wholesale input, and no price — because
22
+ * price is not a category feature anywhere, and a row was only ever drawn
23
+ * for a feature.
24
+ *
25
+ * Plus every slug the URL already carries a range for.
16
26
  *
17
27
  * That last clause is the same rule `buildFacetGroups` follows for a filtered
18
28
  * slug that fell out of the plan: a constraint that is ACTIVE must always have
@@ -35,6 +45,15 @@ export const RANGE_FEATURE_TYPES = [
35
45
  "float",
36
46
  "convertible_unit",
37
47
  ];
48
+ /**
49
+ * i18n key for a core axis's own name.
50
+ *
51
+ * A core axis has no FeatureDef and therefore no `name` to translate. The
52
+ * label is this package's, because the axis is this package's.
53
+ */
54
+ export function coreRangeLabelKey(slug) {
55
+ return `search.range.${slug}`;
56
+ }
38
57
  function num(value) {
39
58
  return typeof value === "number" && Number.isFinite(value) ? value : undefined;
40
59
  }
@@ -53,17 +72,27 @@ export function isRangeFeature(feature) {
53
72
  return type !== undefined && RANGE_FEATURE_TYPES.includes(type);
54
73
  }
55
74
  /**
56
- * The range rows for the current search: every numeric feature of the
57
- * category, in the schema's own order, plus any slug the URL constrains that
58
- * the schema does not explain.
75
+ * The range rows for the current search, in the order they should be read:
76
+ * the CORE axes the answer declares, then every numeric feature of the
77
+ * category in the schema's own order, then any slug the URL constrains that
78
+ * neither explains.
79
+ *
80
+ * Core first is not cosmetic. On the board this was measured against, the
81
+ * seven numeric attributes a phone category happens to declare are all
82
+ * shipping and wholesale inputs; the one number a phone buyer narrows by is
83
+ * the price, and it belongs above them.
59
84
  */
60
85
  export function buildRangeGroups(input) {
61
86
  const bySlug = new Map();
62
87
  for (const feature of input.categoryFeatures ?? [])
63
88
  bySlug.set(feature.slug, feature);
64
- const slugs = [];
89
+ const core = new Set(input.coreRanges ?? []);
90
+ const slugs = [...core];
65
91
  for (const feature of input.categoryFeatures ?? []) {
66
- if (isRangeFeature(feature))
92
+ // A core slug shadows a same-named attribute — which is exactly what the
93
+ // server does with it (`index_schema.CORE_RANGE_FIELDS` reserves the
94
+ // slug), so drawing both would put two controls over one filter.
95
+ if (isRangeFeature(feature) && !core.has(feature.slug))
67
96
  slugs.push(feature.slug);
68
97
  }
69
98
  for (const slug of Object.keys(input.state.ranges)) {
@@ -71,20 +100,32 @@ export function buildRangeGroups(input) {
71
100
  slugs.push(slug);
72
101
  }
73
102
  return slugs.map((slug) => {
74
- const feature = bySlug.get(slug);
103
+ const isCore = core.has(slug);
104
+ const feature = isCore ? undefined : bySlug.get(slug);
75
105
  const config = feature === undefined ? {} : featureConfig(feature);
76
106
  const applied = input.state.ranges[slug];
77
107
  return {
78
108
  slug,
79
- label: feature === undefined ? slug : translate(input.t, featureName(feature)),
109
+ label: isCore
110
+ ? translate(input.t, coreRangeLabelKey(slug))
111
+ : feature === undefined
112
+ ? slug
113
+ : translate(input.t, featureName(feature)),
80
114
  feature,
81
115
  from: applied?.from,
82
116
  to: applied?.to,
83
117
  min: num(config["min"]),
84
118
  max: num(config["max"]),
85
- unit: str(config["postfix"]) ?? str(config["unit_m"]) ?? str(config["unit_i"]),
119
+ // A core money axis carries a CURRENCY, not a unit suffix: "" is
120
+ // formatted from the code for the reader's locale, a unit suffix is a literal
121
+ // the category author typed.
122
+ unit: isCore
123
+ ? undefined
124
+ : (str(config["postfix"]) ?? str(config["unit_m"]) ?? str(config["unit_i"])),
86
125
  step: feature !== undefined && featureType(feature) === "int" ? 1 : undefined,
87
126
  active: applied !== undefined,
127
+ core: isCore,
128
+ currency: isCore ? str(input.currency) : undefined,
88
129
  };
89
130
  });
90
131
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ranges.js","sourceRoot":"","sources":["../../src/state/ranges.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ranges.js","sourceRoot":"","sources":["../../src/state/ranges.ts"],"names":[],"mappings":"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"}
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
- # @stapel/search-react 0.10.0
1
+ # @stapel/search-react 0.12.0
2
2
 
3
- Headless React flow pair for stapel-search (contract >=0.3 <0.4) — business + state, zero visual opinion.
3
+ Headless React flow pair for stapel-search (contract >=0.7 <0.8) — business + state, zero visual opinion.
4
4
  Built on @stapel/core: typed client + StapelApiError envelope, auth token refresh,
5
5
  verification-403 interception, i18n engine, analytics facade, TanStack Query layer.
6
6
 
package/manifest.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "$generated": "by scripts/gen-manifest.mjs — do not edit; drift-gated (pnpm gen:manifest:check)",
3
3
  "package": "@stapel/search-react",
4
- "version": "0.10.0",
4
+ "version": "0.12.0",
5
5
  "backend": {
6
6
  "module": "stapel-search",
7
- "contract": ">=0.3 <0.4"
7
+ "contract": ">=0.7 <0.8"
8
8
  },
9
9
  "layers": [
10
10
  "api",
@@ -703,6 +703,10 @@
703
703
  "error.500.internal",
704
704
  "error.503.mandate_unavailable",
705
705
  "error.503.search_backend_unavailable",
706
+ "search.box.categories",
707
+ "search.box.category_count",
708
+ "search.box.category_count.one",
709
+ "search.box.category_count.other",
706
710
  "search.box.clear",
707
711
  "search.box.label",
708
712
  "search.box.placeholder",
@@ -745,6 +749,7 @@
745
749
  "search.filters.chips_label",
746
750
  "search.filters.dismiss",
747
751
  "search.filters.open",
752
+ "search.filters.short",
748
753
  "search.filters.show_count",
749
754
  "search.filters.show_count.one",
750
755
  "search.filters.show_count.other",
@@ -763,6 +768,10 @@
763
768
  "search.limit.from_link",
764
769
  "search.limit.label",
765
770
  "search.limit.option",
771
+ "search.nav.ranking",
772
+ "search.nav.ranking.short",
773
+ "search.nav.results",
774
+ "search.range.price",
766
775
  "search.ranking.applies_to",
767
776
  "search.ranking.empty",
768
777
  "search.ranking.inactive",
@@ -818,6 +827,7 @@
818
827
  ],
819
828
  "exports": {
820
829
  "runtime": [
830
+ "FACETABLE_FEATURE_TYPES",
821
831
  "FILTER_PREFIX",
822
832
  "FacetPanel",
823
833
  "RANGE_FEATURE_TYPES",
@@ -838,6 +848,8 @@
838
848
  "SEARCH_QUERY_MAX_CHARS",
839
849
  "SEARCH_SORTS",
840
850
  "SEARCH_WINDOW_EXCEEDED",
851
+ "SUGGEST_DEGRADED_CATEGORIES",
852
+ "SUGGEST_DEGRADED_ROLLUP",
841
853
  "SUGGEST_MAX_LIMIT",
842
854
  "SUGGEST_MIN_CHARS",
843
855
  "SearchProvider",
@@ -848,22 +860,27 @@
848
860
  "buildFacetGroups",
849
861
  "buildRangeGroups",
850
862
  "clearFilters",
863
+ "coreRangeLabelKey",
851
864
  "countIsEstimate",
852
865
  "countKind",
853
866
  "countQueryState",
854
867
  "createSearchApi",
855
868
  "createSearchRuntime",
869
+ "degradationAudience",
856
870
  "degradationMessageKey",
857
871
  "explainSearchError",
858
872
  "facetOptionLabel",
859
873
  "isCountNuanceOnly",
874
+ "isFacetableFeature",
860
875
  "isRangeFeature",
861
876
  "isRangeUsable",
862
877
  "navEntries",
878
+ "offerableCategories",
863
879
  "ownsParam",
864
880
  "parseDegradations",
865
881
  "parseSearchState",
866
882
  "patchSearchState",
883
+ "readerFacing",
867
884
  "registerSearchI18n",
868
885
  "searchErrorBundleEn",
869
886
  "searchI18nBundleEn",
@@ -871,9 +888,11 @@
871
888
  "searchQueryParams",
872
889
  "setFilterValues",
873
890
  "setRangeValue",
891
+ "suggestTerms",
874
892
  "toggleFilterValue",
875
893
  "useAppliedSort",
876
894
  "useFacetPanel",
895
+ "useHostFacetLabels",
877
896
  "useRankingDisclosure",
878
897
  "useSearchAnalytics",
879
898
  "useSearchApi",
@@ -890,6 +909,8 @@
890
909
  "BuildRangeGroupsInput",
891
910
  "CreateSearchRuntimeOptions",
892
911
  "FacetGroup",
912
+ "FacetLabelRequest",
913
+ "FacetLabelResolver",
893
914
  "FacetMeta",
894
915
  "FacetOption",
895
916
  "FacetPanelBag",
@@ -908,6 +929,7 @@
908
929
  "SearchCountKind",
909
930
  "SearchCountState",
910
931
  "SearchDegradation",
932
+ "SearchDegradationAudience",
911
933
  "SearchDegradationKind",
912
934
  "SearchErrorCode",
913
935
  "SearchErrorSpec",
@@ -929,6 +951,9 @@
929
951
  "SearchStateIssueCode",
930
952
  "SearchStatePatch",
931
953
  "SearchStateProviderProps",
954
+ "SuggestAnswer",
955
+ "SuggestCategory",
956
+ "SuggestCategoryMatch",
932
957
  "SuggestParams",
933
958
  "SuggestResponse",
934
959
  "UseSearchBoxOptions",
package/nav-manifest.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "package": "@stapel/search-react",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "entries": [
5
5
  {
6
6
  "id": "search.results",
7
- "labelKey": "search.results.title",
7
+ "labelKey": "search.nav.results",
8
8
  "icon": "SearchOutlined",
9
9
  "route": {
10
10
  "path": "/s"
@@ -23,7 +23,8 @@
23
23
  },
24
24
  {
25
25
  "id": "search.ranking",
26
- "labelKey": "search.ranking.title",
26
+ "labelKey": "search.nav.ranking",
27
+ "shortLabelKey": "search.nav.ranking.short",
27
28
  "icon": "OrderedListOutlined",
28
29
  "route": {
29
30
  "path": "/ranking-disclosure"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stapel/search-react",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "Headless React pair for stapel-search: a typed query client, TanStack Query hooks, and a URL-first state codec that makes a search shareable by construction (filters, ranges, geo, sort and the keyset cursor all live in the query string). Drill-down facets rendered with their remaining counts and with the server's own honesty flags — approximate, skipped, degraded — never swallowed; keyset pagination with the window refusal named; DSA Art. 26 `promoted` marking carried into every card slot and the P2B Art. 5 ranking disclosure exposed as data. Zero visual opinion in the main entry; an opt-in /default subpath ships the antd skin, and /router binds the codec to react-router's useSearchParams.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -52,14 +52,14 @@
52
52
  ],
53
53
  "size-limit": [
54
54
  {
55
- "name": "index — the headless pair (client + hooks + URL codec + facet model): no antd, no react-router",
55
+ "name": "index — the headless pair (client + hooks + URL codec + facet model incl. the facetability rule and the host label seam): no antd, no react-router",
56
56
  "path": "dist/index.js",
57
- "limit": "10 KB"
57
+ "limit": "10.5 KB"
58
58
  },
59
59
  {
60
- "name": "default — the antd skin (query box + typeahead, filters incl. ranges/geo/category slots + the phone chip row and the location summary row, results incl. the view switch, ranking) must stay out of the main bundle",
60
+ "name": "default — the antd skin (query box + a typeahead that offers CATEGORY destinations with their live counts, filters incl. ranges/geo/category slots + the phone chip row with its leading category chip and the location summary row, results incl. the view switch, ranking) must stay out of the main bundle",
61
61
  "path": "dist/default/index.js",
62
- "limit": "18 KB"
62
+ "limit": "19.5 KB"
63
63
  },
64
64
  {
65
65
  "name": "router — the react-router binding is opt-in; the main entry must never pull a router",
@@ -78,10 +78,10 @@
78
78
  }
79
79
  ],
80
80
  "peerDependencies": {
81
- "@stapel/attributes-react": ">=0.1.0",
81
+ "@stapel/attributes-react": ">=0.4.0",
82
82
  "@stapel/core": ">=0.18.1 <1.0.0",
83
83
  "@stapel/image": ">=0.3.0",
84
- "@stapel/tokens-antd": ">=0.7.0",
84
+ "@stapel/tokens-antd": ">=0.11.0",
85
85
  "@tanstack/react-query": "^5.0.0",
86
86
  "antd": ">=5.20.0 <7",
87
87
  "react": ">=19",
@@ -115,12 +115,12 @@
115
115
  "size-limit": "^11.2.0",
116
116
  "typescript": "^5.8.3",
117
117
  "vitest": "^3.2.4",
118
- "@stapel/attributes-react": "^0.5.0",
119
- "@stapel/core": "^0.20.0",
118
+ "@stapel/attributes-react": "^0.6.0",
119
+ "@stapel/core": "^0.22.0",
120
120
  "@stapel/image": "^0.4.2",
121
121
  "@stapel/showcase": "^0.3.0",
122
122
  "@stapel/tokens": "^0.6.0",
123
- "@stapel/tokens-antd": "^0.9.0"
123
+ "@stapel/tokens-antd": "^0.11.0"
124
124
  },
125
125
  "engines": {
126
126
  "node": ">=22"