@stapel/search-react 0.9.1 → 0.11.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 (94) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/dist/api/generated/schema.d.ts +16 -0
  3. package/dist/api/generated/schema.d.ts.map +1 -1
  4. package/dist/default/DegradationNotice.d.ts +22 -13
  5. package/dist/default/DegradationNotice.d.ts.map +1 -1
  6. package/dist/default/DegradationNotice.js +7 -8
  7. package/dist/default/DegradationNotice.js.map +1 -1
  8. package/dist/default/FacetPanelPane.d.ts.map +1 -1
  9. package/dist/default/FacetPanelPane.js +27 -16
  10. package/dist/default/FacetPanelPane.js.map +1 -1
  11. package/dist/default/FilterChips.d.ts.map +1 -1
  12. package/dist/default/FilterChips.js +13 -23
  13. package/dist/default/FilterChips.js.map +1 -1
  14. package/dist/default/LocationSummaryLine.d.ts +24 -0
  15. package/dist/default/LocationSummaryLine.d.ts.map +1 -0
  16. package/dist/default/LocationSummaryLine.js +83 -0
  17. package/dist/default/LocationSummaryLine.js.map +1 -0
  18. package/dist/default/RangeFilterRow.d.ts.map +1 -1
  19. package/dist/default/RangeFilterRow.js +49 -5
  20. package/dist/default/RangeFilterRow.js.map +1 -1
  21. package/dist/default/SearchPage.d.ts +19 -0
  22. package/dist/default/SearchPage.d.ts.map +1 -1
  23. package/dist/default/SearchPage.js +6 -6
  24. package/dist/default/SearchPage.js.map +1 -1
  25. package/dist/default/geoSheet.d.ts +61 -0
  26. package/dist/default/geoSheet.d.ts.map +1 -0
  27. package/dist/default/geoSheet.js +63 -0
  28. package/dist/default/geoSheet.js.map +1 -0
  29. package/dist/default/index.d.ts +2 -0
  30. package/dist/default/index.d.ts.map +1 -1
  31. package/dist/default/index.js +1 -0
  32. package/dist/default/index.js.map +1 -1
  33. package/dist/headless/FacetPanel.d.ts +16 -0
  34. package/dist/headless/FacetPanel.d.ts.map +1 -1
  35. package/dist/headless/FacetPanel.js +7 -0
  36. package/dist/headless/FacetPanel.js.map +1 -1
  37. package/dist/headless/useSearchCount.d.ts +44 -0
  38. package/dist/headless/useSearchCount.d.ts.map +1 -0
  39. package/dist/headless/useSearchCount.js +121 -0
  40. package/dist/headless/useSearchCount.js.map +1 -0
  41. package/dist/i18n/es.d.ts.map +1 -1
  42. package/dist/i18n/es.js +2 -0
  43. package/dist/i18n/es.js.map +1 -1
  44. package/dist/i18n/keys.d.ts +13 -0
  45. package/dist/i18n/keys.d.ts.map +1 -1
  46. package/dist/i18n/keys.js +15 -0
  47. package/dist/i18n/keys.js.map +1 -1
  48. package/dist/i18n/ru.d.ts.map +1 -1
  49. package/dist/i18n/ru.js +2 -0
  50. package/dist/i18n/ru.js.map +1 -1
  51. package/dist/index.d.ts +6 -4
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +4 -3
  54. package/dist/index.js.map +1 -1
  55. package/dist/state/degradations.d.ts +32 -0
  56. package/dist/state/degradations.d.ts.map +1 -1
  57. package/dist/state/degradations.js +33 -0
  58. package/dist/state/degradations.js.map +1 -1
  59. package/dist/state/facets.d.ts +23 -0
  60. package/dist/state/facets.d.ts.map +1 -1
  61. package/dist/state/facets.js +59 -8
  62. package/dist/state/facets.js.map +1 -1
  63. package/dist/state/limits.d.ts +21 -0
  64. package/dist/state/limits.d.ts.map +1 -1
  65. package/dist/state/limits.js +21 -0
  66. package/dist/state/limits.js.map +1 -1
  67. package/dist/state/ranges.d.ts +45 -3
  68. package/dist/state/ranges.d.ts.map +1 -1
  69. package/dist/state/ranges.js +55 -14
  70. package/dist/state/ranges.js.map +1 -1
  71. package/llms.txt +3 -2
  72. package/manifest.json +36 -3
  73. package/nav-manifest.json +1 -1
  74. package/package.json +6 -6
  75. package/src/analytics/generated/events.json +1 -1
  76. package/src/api/generated/schema.ts +16 -0
  77. package/src/default/DegradationNotice.tsx +30 -20
  78. package/src/default/FacetPanelPane.tsx +39 -23
  79. package/src/default/FilterChips.tsx +22 -56
  80. package/src/default/LocationSummaryLine.tsx +187 -0
  81. package/src/default/RangeFilterRow.tsx +61 -3
  82. package/src/default/SearchPage.tsx +33 -3
  83. package/src/default/geoSheet.tsx +161 -0
  84. package/src/default/index.ts +3 -0
  85. package/src/headless/FacetPanel.tsx +24 -0
  86. package/src/headless/useSearchCount.ts +189 -0
  87. package/src/i18n/es.ts +2 -0
  88. package/src/i18n/keys.ts +15 -0
  89. package/src/i18n/ru.ts +2 -0
  90. package/src/index.ts +15 -1
  91. package/src/state/degradations.ts +56 -0
  92. package/src/state/facets.ts +96 -8
  93. package/src/state/limits.ts +23 -0
  94. package/src/state/ranges.ts +85 -15
@@ -8,12 +8,20 @@
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 come from two places, in that order.** The category's
12
+ * feature schema `categoryFeatures`, the second slot-seam of the pair,
13
+ * filled by the container from `categories-react` (spec §6.2 item 2) —
14
+ * and, under it, the answer's own `facet_labels` (stapel-search 0.4.0+).
15
+ *
16
+ * Until 0.4.0 the server sent `{value: count}` and nothing else, on the
17
+ * reasoning that the container has the schema anyway. That reasoning had
18
+ * a hole the size of a marketplace: the slot is OPTIONAL, a live
19
+ * classified board never filled it, and its buyers read "Condition:
20
+ * **b-u**", "Listing kind: **prodayu-svoe**", "Screen condition:
21
+ * **bez-defektov**" on the SERP and in the filter chips. The schema still
22
+ * wins where it resolves — the client fetched it with its own
23
+ * `Accept-Language` — and the answer's captions are the floor beneath it.
24
+ * Neither invents a label: a value nobody names renders as itself.
17
25
  *
18
26
  * A slug the server SKIPPED (`facet_meta.skipped`, dropped at
19
27
  * `MAX_FACET_FIELDS`) is not counted at all. Its options carry `count: null`,
@@ -27,6 +35,21 @@ function translate(t, key) {
27
35
  const resolved = t(key);
28
36
  return resolved.length > 0 ? resolved : key;
29
37
  }
38
+ /**
39
+ * The caption the ANSWER carries for one option, or `undefined`.
40
+ *
41
+ * `translatable` is the server saying whether `values` holds translation
42
+ * KEYS or literal captions, and it has to be said rather than sniffed:
43
+ * `b.apple` and a rendered caption are both strings, and guessing wrong prints either a
44
+ * dotted key or an untranslated word at a buyer.
45
+ */
46
+ function serverLabel(labels, slug, value, t) {
47
+ const entry = labels?.[slug];
48
+ const caption = entry?.values[value];
49
+ if (caption === undefined || caption.length === 0)
50
+ return undefined;
51
+ return entry?.translatable === true ? translate(t, caption) : caption;
52
+ }
30
53
  /**
31
54
  * The declared option ORDER for a closed set, if the schema declares one.
32
55
  *
@@ -54,6 +77,22 @@ function declaredOptionValues(feature) {
54
77
  }
55
78
  return out;
56
79
  }
80
+ /**
81
+ * One option's caption: schema, then answer, then the raw value.
82
+ *
83
+ * Order matters and is deliberate. Both sources are the same category
84
+ * config, but the client fetched its copy with its own `Accept-Language`, so
85
+ * where the host threaded a schema through and it names the value, that is
86
+ * the better-localized answer. Where it does not — no schema, a slug the
87
+ * schema omits, a value added since — the server's caption is what stops
88
+ * `b-u` reaching a buyer.
89
+ */
90
+ function resolveLabel(input, feature, slug, value, labelOptions) {
91
+ const viaSchema = facetOptionLabel(feature, value, labelOptions);
92
+ if (feature !== undefined && viaSchema !== value)
93
+ return viaSchema;
94
+ return serverLabel(input.facetLabels, slug, value, input.t) ?? viaSchema;
95
+ }
57
96
  /**
58
97
  * A facet value as a person reads it, through
59
98
  * `@stapel/attributes-react`'s `formatFeatureValue` — the same formatter the
@@ -129,7 +168,14 @@ export function buildFacetGroups(input) {
129
168
  const counts = input.facets[slug] ?? {};
130
169
  const counted = !skipped.has(slug) && slug in input.facets;
131
170
  const selected = input.state.filters[slug] ?? [];
132
- const declared = declaredOptionValues(feature);
171
+ // The authored option order, from whichever copy of the category config
172
+ // this page has. An authored list reshuffled by count is a size chart
173
+ // that moves on every click, and until 0.11 a host that passed no schema
174
+ // got exactly that.
175
+ const fromSchema = declaredOptionValues(feature);
176
+ const declared = fromSchema.length > 0
177
+ ? fromSchema
178
+ : Object.keys(input.facetLabels?.[slug]?.values ?? {});
133
179
  const values = [];
134
180
  const push = (value) => {
135
181
  if (!values.includes(value))
@@ -162,7 +208,12 @@ export function buildFacetGroups(input) {
162
208
  options: values.map((value) => ({
163
209
  value,
164
210
  count: counted ? (counts[value] ?? 0) : null,
165
- label: facetOptionLabel(feature, value, labelOptions),
211
+ // The host's schema first when it actually resolves the value, the
212
+ // answer's caption when it does not (or when there is no schema at
213
+ // all), the raw value when neither knows. `facetOptionLabel` returns
214
+ // the value unchanged for an option it cannot name, which is what
215
+ // makes "did it resolve?" answerable without a second lookup.
216
+ label: resolveLabel(input, feature, slug, value, labelOptions),
166
217
  selected: selected.includes(value),
167
218
  })),
168
219
  };
@@ -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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"}
@@ -22,6 +22,27 @@ export declare const SEARCH_DEFAULT_PAGE_SIZE = 24;
22
22
  export declare const SEARCH_MAX_PAGE_SIZE = 100;
23
23
  /** `services.suggest` clamps `limit` into `1..25`. */
24
24
  export declare const SUGGEST_MAX_LIMIT = 25;
25
+ /**
26
+ * The page a COUNT read asks for.
27
+ *
28
+ * There is no count-only endpoint (`SearchApi` is query/suggest/ranking), so
29
+ * `useSearchCount` rides the ordinary query and takes the total out of the
30
+ * envelope. One row is the smallest page the endpoint documents — `limit=0` is
31
+ * not a page size — and it is the difference between serializing one card
32
+ * nobody renders and serializing the default twenty-four.
33
+ */
34
+ export declare const SEARCH_COUNT_PAGE_SIZE = 1;
35
+ /**
36
+ * How long a composing panel has to stop changing before its count is asked
37
+ * for.
38
+ *
39
+ * Shorter than the search commit (350ms): a count follows discrete choices — a
40
+ * select, a chip, a range end — rather than keystrokes, and a button whose
41
+ * number arrives a third of a second after the choice reads as broken. Longer
42
+ * than zero: the read is a full query (see `SEARCH_COUNT_PAGE_SIZE`), so a
43
+ * person walking a select's options must not spend one search per option.
44
+ */
45
+ export declare const SEARCH_COUNT_DEBOUNCE_MS = 250;
25
46
  /**
26
47
  * The shortest prefix worth asking the index about.
27
48
  *
@@ -1 +1 @@
1
- {"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../../src/state/limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,iFAAiF;AACjF,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C,iEAAiE;AACjE,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAE3C,+DAA+D;AAC/D,eAAO,MAAM,oBAAoB,MAAM,CAAC;AAExC,sDAAsD;AACtD,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,IAAI,CAAC;AAEnC,8DAA8D;AAC9D,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C;;;;;;;GAOG;AACH,eAAO,MAAM,8BAA8B,MAAM,CAAC"}
1
+ {"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../../src/state/limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,iFAAiF;AACjF,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C,iEAAiE;AACjE,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAE3C,+DAA+D;AAC/D,eAAO,MAAM,oBAAoB,MAAM,CAAC;AAExC,sDAAsD;AACtD,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAE5C;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,IAAI,CAAC;AAEnC,8DAA8D;AAC9D,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C;;;;;;;GAOG;AACH,eAAO,MAAM,8BAA8B,MAAM,CAAC"}
@@ -22,6 +22,27 @@ export const SEARCH_DEFAULT_PAGE_SIZE = 24;
22
22
  export const SEARCH_MAX_PAGE_SIZE = 100;
23
23
  /** `services.suggest` clamps `limit` into `1..25`. */
24
24
  export const SUGGEST_MAX_LIMIT = 25;
25
+ /**
26
+ * The page a COUNT read asks for.
27
+ *
28
+ * There is no count-only endpoint (`SearchApi` is query/suggest/ranking), so
29
+ * `useSearchCount` rides the ordinary query and takes the total out of the
30
+ * envelope. One row is the smallest page the endpoint documents — `limit=0` is
31
+ * not a page size — and it is the difference between serializing one card
32
+ * nobody renders and serializing the default twenty-four.
33
+ */
34
+ export const SEARCH_COUNT_PAGE_SIZE = 1;
35
+ /**
36
+ * How long a composing panel has to stop changing before its count is asked
37
+ * for.
38
+ *
39
+ * Shorter than the search commit (350ms): a count follows discrete choices — a
40
+ * select, a chip, a range end — rather than keystrokes, and a button whose
41
+ * number arrives a third of a second after the choice reads as broken. Longer
42
+ * than zero: the read is a full query (see `SEARCH_COUNT_PAGE_SIZE`), so a
43
+ * person walking a select's options must not spend one search per option.
44
+ */
45
+ export const SEARCH_COUNT_DEBOUNCE_MS = 250;
25
46
  /**
26
47
  * The shortest prefix worth asking the index about.
27
48
  *
@@ -1 +1 @@
1
- {"version":3,"file":"limits.js","sourceRoot":"","sources":["../../src/state/limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,iFAAiF;AACjF,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,CAAC;AAE1C,iEAAiE;AACjE,MAAM,CAAC,MAAM,wBAAwB,GAAG,EAAE,CAAC;AAE3C,+DAA+D;AAC/D,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,CAAC;AAExC,sDAAsD;AACtD,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAEpC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAEnC,8DAA8D;AAC9D,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,CAAC;AAE1C;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAG,GAAG,CAAC"}
1
+ {"version":3,"file":"limits.js","sourceRoot":"","sources":["../../src/state/limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,iFAAiF;AACjF,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,CAAC;AAE1C,iEAAiE;AACjE,MAAM,CAAC,MAAM,wBAAwB,GAAG,EAAE,CAAC;AAE3C,+DAA+D;AAC/D,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,CAAC;AAExC,sDAAsD;AACtD,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAEpC;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC;AAExC;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,CAAC;AAE5C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAEnC,8DAA8D;AAC9D,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,CAAC;AAE1C;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAG,GAAG,CAAC"}
@@ -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.9.1
1
+ # @stapel/search-react 0.11.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.4 <0.5) — 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
 
@@ -81,6 +81,7 @@ const { tracked } = useTracked();
81
81
  - search.facet-panel-pane → <FacetPanelPane> [open-search|narrowed] demo/FacetPanelPane.demo.tsx
82
82
  - search.filter-sheet → <SearchPage> [open] demo/FilterSheet.demo.tsx
83
83
  - search.language-select → <LanguageSelect> [offered|from-a-link] demo/LanguageSelect.demo.tsx
84
+ - search.location-summary → <LocationSummaryLine> [everywhere|placed|narrowed|wide] demo/LocationSummaryLine.demo.tsx
84
85
  - search.page → <SearchPage> [desktop|phone|unreadable-link] demo/SearchPage.demo.tsx
85
86
  - search.page-size-select → <PageSizeSelect> [ladder|off-ladder] demo/PageSizeSelect.demo.tsx
86
87
  - search.range-filter-row → <RangeFilterRow> [untouched|applied] demo/RangeFilterRow.demo.tsx
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.9.1",
4
+ "version": "0.11.0",
5
5
  "backend": {
6
6
  "module": "stapel-search",
7
- "contract": ">=0.3 <0.4"
7
+ "contract": ">=0.4 <0.5"
8
8
  },
9
9
  "layers": [
10
10
  "api",
@@ -496,6 +496,26 @@
496
496
  ],
497
497
  "source": "demo/LanguageSelect.demo.tsx"
498
498
  },
499
+ {
500
+ "id": "search.location-summary",
501
+ "title": "Location summary line",
502
+ "description": "Where this search is looking, on a row of its own above the chips — because the chip row scrolls and location is the one constraint that changes what a result MEANS rather than narrowing a set. It prints the name the container handed it plus the radius, never a coordinate, and it opens the same location sheet the geo chip opens. On the right, the filter affordance carries a COUNT rather than the chip row's dot: this row has the width to say how many.",
503
+ "component": "LocationSummaryLine",
504
+ "covers": [
505
+ "SearchStateProvider"
506
+ ],
507
+ "tokens": [
508
+ "brand",
509
+ "text-muted"
510
+ ],
511
+ "variants": [
512
+ "everywhere",
513
+ "placed",
514
+ "narrowed",
515
+ "wide"
516
+ ],
517
+ "source": "demo/LocationSummaryLine.demo.tsx"
518
+ },
499
519
  {
500
520
  "id": "search.page",
501
521
  "title": "Search page",
@@ -734,6 +754,7 @@
734
754
  "search.geo.box",
735
755
  "search.geo.chosen_place",
736
756
  "search.geo.clear",
757
+ "search.geo.everywhere",
737
758
  "search.geo.radius_km",
738
759
  "search.geo.radius_label",
739
760
  "search.geo.title",
@@ -742,6 +763,7 @@
742
763
  "search.limit.from_link",
743
764
  "search.limit.label",
744
765
  "search.limit.option",
766
+ "search.range.price",
745
767
  "search.ranking.applies_to",
746
768
  "search.ranking.empty",
747
769
  "search.ranking.inactive",
@@ -805,6 +827,8 @@
805
827
  "SEARCH_BACKEND_UNAVAILABLE",
806
828
  "SEARCH_BOX_DEBOUNCE_MS",
807
829
  "SEARCH_BOX_SUGGEST_DEBOUNCE_MS",
830
+ "SEARCH_COUNT_DEBOUNCE_MS",
831
+ "SEARCH_COUNT_PAGE_SIZE",
808
832
  "SEARCH_DEFAULT_PAGE_SIZE",
809
833
  "SEARCH_ERRORS",
810
834
  "SEARCH_ERROR_CODES",
@@ -825,10 +849,13 @@
825
849
  "buildFacetGroups",
826
850
  "buildRangeGroups",
827
851
  "clearFilters",
852
+ "coreRangeLabelKey",
828
853
  "countIsEstimate",
829
854
  "countKind",
855
+ "countQueryState",
830
856
  "createSearchApi",
831
857
  "createSearchRuntime",
858
+ "degradationAudience",
832
859
  "degradationMessageKey",
833
860
  "explainSearchError",
834
861
  "facetOptionLabel",
@@ -840,6 +867,7 @@
840
867
  "parseDegradations",
841
868
  "parseSearchState",
842
869
  "patchSearchState",
870
+ "readerFacing",
843
871
  "registerSearchI18n",
844
872
  "searchErrorBundleEn",
845
873
  "searchI18nBundleEn",
@@ -854,6 +882,7 @@
854
882
  "useSearchAnalytics",
855
883
  "useSearchApi",
856
884
  "useSearchBox",
885
+ "useSearchCount",
857
886
  "useSearchQuery",
858
887
  "useSearchRuntime",
859
888
  "useSearchState",
@@ -879,8 +908,11 @@
879
908
  "Scorer",
880
909
  "SearchApi",
881
910
  "SearchBoxBag",
911
+ "SearchCount",
882
912
  "SearchCountKind",
913
+ "SearchCountState",
883
914
  "SearchDegradation",
915
+ "SearchDegradationAudience",
884
916
  "SearchDegradationKind",
885
917
  "SearchErrorCode",
886
918
  "SearchErrorSpec",
@@ -904,7 +936,8 @@
904
936
  "SearchStateProviderProps",
905
937
  "SuggestParams",
906
938
  "SuggestResponse",
907
- "UseSearchBoxOptions"
939
+ "UseSearchBoxOptions",
940
+ "UseSearchCountOptions"
908
941
  ]
909
942
  }
910
943
  }
package/nav-manifest.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "package": "@stapel/search-react",
3
- "version": "0.9.1",
3
+ "version": "0.11.0",
4
4
  "entries": [
5
5
  {
6
6
  "id": "search.results",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stapel/search-react",
3
- "version": "0.9.1",
3
+ "version": "0.11.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": {
@@ -57,7 +57,7 @@
57
57
  "limit": "10 KB"
58
58
  },
59
59
  {
60
- "name": "default — the antd skin (query box + typeahead, filters incl. ranges/geo/category slots + the phone chip row, results incl. the view switch, ranking) must stay out of the main bundle",
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",
61
61
  "path": "dist/default/index.js",
62
62
  "limit": "18 KB"
63
63
  },
@@ -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.4.0",
119
- "@stapel/core": "^0.20.0",
118
+ "@stapel/attributes-react": "^0.5.0",
119
+ "@stapel/core": "^0.21.0",
120
120
  "@stapel/image": "^0.4.2",
121
- "@stapel/showcase": "^0.3.0",
122
121
  "@stapel/tokens": "^0.6.0",
123
- "@stapel/tokens-antd": "^0.8.1"
122
+ "@stapel/tokens-antd": "^0.10.0",
123
+ "@stapel/showcase": "^0.3.0"
124
124
  },
125
125
  "engines": {
126
126
  "node": ">=22"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$generated": "by scripts/gen-events.mjs — do not edit; drift-gated (pnpm gen:events:check)",
3
3
  "package": "@stapel/search-react",
4
- "version": "0.9.1",
4
+ "version": "0.11.0",
5
5
  "defined": [],
6
6
  "flows": []
7
7
  }
@@ -124,6 +124,14 @@ export interface paths {
124
124
  export type webhooks = Record<string, never>;
125
125
  export interface components {
126
126
  schemas: {
127
+ /** @description Captions for one slug's option codes. */
128
+ FacetLabels: {
129
+ /** @description True when `values` holds translation KEYS to run through the catalogue; false when it holds literal captions. The reader cannot tell by looking — `b.apple` and `Б/у` are both strings. */
130
+ translatable: boolean;
131
+ values: {
132
+ [key: string]: string;
133
+ };
134
+ };
127
135
  FacetMeta: {
128
136
  /** @description True when counts came from a sample because the candidate set exceeded FACET_CANDIDATE_CAP. */
129
137
  approximate: boolean;
@@ -132,6 +140,8 @@ export interface components {
132
140
  counted: string[];
133
141
  /** @description Plan slugs dropped at MAX_FACET_FIELDS — reported, not vanished. */
134
142
  skipped: string[];
143
+ /** @description Range slugs that address a core document column rather than an attribute (`r.price`). Offer them as filters unconditionally: they exist for every document in every category, which is why they are not in the category's own plan. */
144
+ core_ranges: string[];
135
145
  };
136
146
  HealthResponse: {
137
147
  backend: string;
@@ -208,6 +218,10 @@ export interface components {
208
218
  [key: string]: number;
209
219
  };
210
220
  };
221
+ /** @description {slug: {translatable, values: {value: caption}}} for slugs whose options are inline in the category schema. Absent for a vocabulary-backed slug: its level lives outside the schema and the plan will not invent a caption it has not read. */
222
+ facet_labels: {
223
+ [key: string]: components["schemas"]["FacetLabels"];
224
+ };
211
225
  facet_meta: components["schemas"]["FacetMeta"];
212
226
  next_anchor: string | null;
213
227
  prev_anchor: string | null;
@@ -222,6 +236,8 @@ export interface components {
222
236
  /** @description What the configured engine could not do for this query. */
223
237
  degraded: string[];
224
238
  backend: string;
239
+ /** @description The language whose dictionary and analyzer configuration answered — `lang`, else Accept-Language, else DEFAULT_LANGUAGE. When the fallback is wrong the synonym layer silently does not apply, and this field is the only place the answer says so. */
240
+ language: string;
225
241
  sort: string;
226
242
  took_ms: number;
227
243
  };