@stapel/search-react 0.21.0 → 0.23.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.
- package/CHANGELOG.md +141 -0
- package/MODULE.md +12 -0
- package/README.md +143 -2
- package/dist/api/types.d.ts +37 -3
- package/dist/api/types.d.ts.map +1 -1
- package/dist/api/types.js.map +1 -1
- package/dist/default/FacetGroupControl.d.ts +53 -5
- package/dist/default/FacetGroupControl.d.ts.map +1 -1
- package/dist/default/FacetGroupControl.js +189 -8
- package/dist/default/FacetGroupControl.js.map +1 -1
- package/dist/default/FacetPanelPane.d.ts +41 -0
- package/dist/default/FacetPanelPane.d.ts.map +1 -1
- package/dist/default/FacetPanelPane.js +72 -18
- package/dist/default/FacetPanelPane.js.map +1 -1
- package/dist/default/FilterChips.d.ts.map +1 -1
- package/dist/default/FilterChips.js +2 -2
- package/dist/default/FilterChips.js.map +1 -1
- package/dist/default/PartitionChips.d.ts +37 -0
- package/dist/default/PartitionChips.d.ts.map +1 -0
- package/dist/default/PartitionChips.js +120 -0
- package/dist/default/PartitionChips.js.map +1 -0
- package/dist/default/PopularValues.d.ts +58 -0
- package/dist/default/PopularValues.d.ts.map +1 -0
- package/dist/default/PopularValues.js +46 -0
- package/dist/default/PopularValues.js.map +1 -0
- package/dist/default/RangeFilterRow.d.ts.map +1 -1
- package/dist/default/RangeFilterRow.js +65 -11
- package/dist/default/RangeFilterRow.js.map +1 -1
- package/dist/default/SearchPage.d.ts +49 -0
- package/dist/default/SearchPage.d.ts.map +1 -1
- package/dist/default/SearchPage.js +43 -2
- package/dist/default/SearchPage.js.map +1 -1
- package/dist/default/index.d.ts +7 -3
- package/dist/default/index.d.ts.map +1 -1
- package/dist/default/index.js +8 -3
- package/dist/default/index.js.map +1 -1
- package/dist/headless/useFacetLabels.js +7 -7
- package/dist/headless/useFacetLabels.js.map +1 -1
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +9 -0
- package/dist/i18n/es.js.map +1 -1
- package/dist/i18n/keys.d.ts +52 -0
- package/dist/i18n/keys.d.ts.map +1 -1
- package/dist/i18n/keys.js +62 -0
- package/dist/i18n/keys.js.map +1 -1
- package/dist/i18n/ru.d.ts.map +1 -1
- package/dist/i18n/ru.js +9 -0
- package/dist/i18n/ru.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/state/facets.d.ts +90 -6
- package/dist/state/facets.d.ts.map +1 -1
- package/dist/state/facets.js +216 -15
- package/dist/state/facets.js.map +1 -1
- package/dist/state/ranges.d.ts +22 -0
- package/dist/state/ranges.d.ts.map +1 -1
- package/dist/state/ranges.js +30 -0
- package/dist/state/ranges.js.map +1 -1
- package/dist/state/translit.d.ts +58 -0
- package/dist/state/translit.d.ts.map +1 -0
- package/dist/state/translit.js +115 -0
- package/dist/state/translit.js.map +1 -0
- package/llms.txt +3 -1
- package/manifest.json +45 -1
- package/nav-manifest.json +1 -1
- package/package.json +8 -8
- package/src/analytics/generated/events.json +1 -1
- package/src/api/types.ts +42 -3
- package/src/default/FacetGroupControl.tsx +339 -9
- package/src/default/FacetPanelPane.tsx +131 -19
- package/src/default/FilterChips.tsx +5 -2
- package/src/default/PartitionChips.tsx +204 -0
- package/src/default/PopularValues.tsx +151 -0
- package/src/default/RangeFilterRow.tsx +116 -1
- package/src/default/SearchPage.tsx +61 -2
- package/src/default/index.ts +27 -2
- package/src/headless/useFacetLabels.ts +7 -7
- package/src/i18n/es.ts +9 -0
- package/src/i18n/keys.ts +63 -0
- package/src/i18n/ru.ts +9 -0
- package/src/index.ts +13 -0
- package/src/state/facets.ts +259 -27
- package/src/state/ranges.ts +44 -0
- package/src/state/translit.ts +113 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Matching a typed prefix against a dictionary value ACROSS ALPHABETS.
|
|
3
|
+
*
|
|
4
|
+
* A vocabulary of car makes holds `Toyota`, `Land Rover`, `Timberland` — Latin
|
|
5
|
+
* words, in a catalogue read by people typing Cyrillic. "toyota" spelled in
|
|
6
|
+
* Cyrillic and `Toyota` are the same word said in two scripts, and a
|
|
7
|
+
* `String.includes` over the caption answers "no" to every one of them. The
|
|
8
|
+
* autocatalog has 418 makes: without this, the search box in a dictionary
|
|
9
|
+
* facet is a box that works only for the half of the buyers whose keyboard
|
|
10
|
+
* matches the catalogue's.
|
|
11
|
+
*
|
|
12
|
+
* ── Two keys per word, and why the second one exists ──────────────────────
|
|
13
|
+
*
|
|
14
|
+
* The first key is a plain transliteration: Cyrillic → Latin through the table
|
|
15
|
+
* below, lowercased, everything but letters and digits dropped. That alone
|
|
16
|
+
* gets the Cyrillic spelling of "toyota" to `toiota` and `Toyota` to `toyota`
|
|
17
|
+
* — still not equal, and this is the general case rather than a bad table.
|
|
18
|
+
* Vowels are exactly where two scripts disagree: `i` against `y`, `e` against
|
|
19
|
+
* `a` in a borrowed word, `timberlend` against `timberland`. A vowel-perfect
|
|
20
|
+
* table does not exist, because the disagreement is in the borrowing, not in
|
|
21
|
+
* the letters.
|
|
22
|
+
*
|
|
23
|
+
* So the second key is the CONSONANT SKELETON: the first key with its vowels
|
|
24
|
+
* removed. `timberlend` and `timberland` are both `tmbrlnd`; `toiota` and
|
|
25
|
+
* `toyota` are both `tt`. Digits stay — a skeleton is about letters people
|
|
26
|
+
* disagree on, and `5` is not one.
|
|
27
|
+
*
|
|
28
|
+
* A candidate matches when either key of any of its WORDS starts with the
|
|
29
|
+
* corresponding key of the needle: a typed "rover" in either script finds
|
|
30
|
+
* `Land Rover`, and typing into the box narrows as you go, which is the whole
|
|
31
|
+
* point of a prefix rule. The skeleton arm is skipped for a needle that has no
|
|
32
|
+
* consonants at all — an empty skeleton is a prefix of everything, and a box
|
|
33
|
+
* that matches all 418 makes after one vowel is a box that has stopped
|
|
34
|
+
* answering.
|
|
35
|
+
*
|
|
36
|
+
* Table-driven and dependency-free on purpose: this is 40 pairs of characters,
|
|
37
|
+
* and a transliteration library is a page-weight decision made for a lookup
|
|
38
|
+
* table.
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* One word, transliterated and folded to `[a-z0-9]`.
|
|
42
|
+
*
|
|
43
|
+
* `NFKD` + combining-mark strip first, so `Škoda` and `Skoda` are one word
|
|
44
|
+
* before anything else looks at them.
|
|
45
|
+
*/
|
|
46
|
+
export declare function translitKey(text: string): string;
|
|
47
|
+
/** {@link translitKey} without its vowels — the key two spellings of one
|
|
48
|
+
* borrowed name agree on. */
|
|
49
|
+
export declare function consonantKey(text: string): string;
|
|
50
|
+
/**
|
|
51
|
+
* Does `candidate` answer the typed `needle`?
|
|
52
|
+
*
|
|
53
|
+
* Prefix, not substring: a dictionary of makes searched by substring puts
|
|
54
|
+
* `Great Wall` under `all`. Every word of the candidate is a starting point,
|
|
55
|
+
* and both keys are tried — see the module note.
|
|
56
|
+
*/
|
|
57
|
+
export declare function translitPrefixMatch(needle: string, candidate: string): boolean;
|
|
58
|
+
//# sourceMappingURL=translit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"translit.d.ts","sourceRoot":"","sources":["../../src/state/translit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAuBH;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAYhD;AAED;6BAC6B;AAC7B,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIjD;AAQD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAU9E"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Matching a typed prefix against a dictionary value ACROSS ALPHABETS.
|
|
3
|
+
*
|
|
4
|
+
* A vocabulary of car makes holds `Toyota`, `Land Rover`, `Timberland` — Latin
|
|
5
|
+
* words, in a catalogue read by people typing Cyrillic. "toyota" spelled in
|
|
6
|
+
* Cyrillic and `Toyota` are the same word said in two scripts, and a
|
|
7
|
+
* `String.includes` over the caption answers "no" to every one of them. The
|
|
8
|
+
* autocatalog has 418 makes: without this, the search box in a dictionary
|
|
9
|
+
* facet is a box that works only for the half of the buyers whose keyboard
|
|
10
|
+
* matches the catalogue's.
|
|
11
|
+
*
|
|
12
|
+
* ── Two keys per word, and why the second one exists ──────────────────────
|
|
13
|
+
*
|
|
14
|
+
* The first key is a plain transliteration: Cyrillic → Latin through the table
|
|
15
|
+
* below, lowercased, everything but letters and digits dropped. That alone
|
|
16
|
+
* gets the Cyrillic spelling of "toyota" to `toiota` and `Toyota` to `toyota`
|
|
17
|
+
* — still not equal, and this is the general case rather than a bad table.
|
|
18
|
+
* Vowels are exactly where two scripts disagree: `i` against `y`, `e` against
|
|
19
|
+
* `a` in a borrowed word, `timberlend` against `timberland`. A vowel-perfect
|
|
20
|
+
* table does not exist, because the disagreement is in the borrowing, not in
|
|
21
|
+
* the letters.
|
|
22
|
+
*
|
|
23
|
+
* So the second key is the CONSONANT SKELETON: the first key with its vowels
|
|
24
|
+
* removed. `timberlend` and `timberland` are both `tmbrlnd`; `toiota` and
|
|
25
|
+
* `toyota` are both `tt`. Digits stay — a skeleton is about letters people
|
|
26
|
+
* disagree on, and `5` is not one.
|
|
27
|
+
*
|
|
28
|
+
* A candidate matches when either key of any of its WORDS starts with the
|
|
29
|
+
* corresponding key of the needle: a typed "rover" in either script finds
|
|
30
|
+
* `Land Rover`, and typing into the box narrows as you go, which is the whole
|
|
31
|
+
* point of a prefix rule. The skeleton arm is skipped for a needle that has no
|
|
32
|
+
* consonants at all — an empty skeleton is a prefix of everything, and a box
|
|
33
|
+
* that matches all 418 makes after one vowel is a box that has stopped
|
|
34
|
+
* answering.
|
|
35
|
+
*
|
|
36
|
+
* Table-driven and dependency-free on purpose: this is 40 pairs of characters,
|
|
37
|
+
* and a transliteration library is a page-weight decision made for a lookup
|
|
38
|
+
* table.
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* Cyrillic → Latin, chosen for how the two scripts spell the SAME borrowed
|
|
42
|
+
* name rather than for a standard's fidelity: `kha` is `h` (Honda, not
|
|
43
|
+
* Khonda), `tse` is `c`, the two signs are nothing. Any letter absent here is
|
|
44
|
+
* kept as it is, so a Latin caption passes through untouched. Keys are
|
|
45
|
+
* quoted — the fleet's source is English, and a Cyrillic identifier is not.
|
|
46
|
+
*/
|
|
47
|
+
const CYRILLIC_TO_LATIN = {
|
|
48
|
+
"а": "a", "б": "b", "в": "v", "г": "g", "д": "d", "е": "e", "ё": "e",
|
|
49
|
+
"ж": "zh", "з": "z", "и": "i", "й": "i", "к": "k", "л": "l", "м": "m",
|
|
50
|
+
"н": "n", "о": "o", "п": "p", "р": "r", "с": "s", "т": "t", "у": "u",
|
|
51
|
+
"ф": "f", "х": "h", "ц": "c", "ч": "ch", "ш": "sh", "щ": "sh", "ъ": "",
|
|
52
|
+
"ы": "y", "ь": "", "э": "e", "ю": "u", "я": "a",
|
|
53
|
+
// Ukrainian/Belarusian letters a shared vocabulary can carry.
|
|
54
|
+
"і": "i", "ї": "i", "є": "e", "ґ": "g", "ў": "u",
|
|
55
|
+
};
|
|
56
|
+
/** Vowels of the transliterated form — `y` included, because it stands for a
|
|
57
|
+
* Cyrillic semi-vowel as often as it stands for a consonant. */
|
|
58
|
+
const VOWELS = new Set(["a", "e", "i", "o", "u", "y"]);
|
|
59
|
+
/**
|
|
60
|
+
* One word, transliterated and folded to `[a-z0-9]`.
|
|
61
|
+
*
|
|
62
|
+
* `NFKD` + combining-mark strip first, so `Škoda` and `Skoda` are one word
|
|
63
|
+
* before anything else looks at them.
|
|
64
|
+
*/
|
|
65
|
+
export function translitKey(text) {
|
|
66
|
+
const folded = text
|
|
67
|
+
.normalize("NFKD")
|
|
68
|
+
.replace(/[\u0300-\u036f]/g, "")
|
|
69
|
+
.toLowerCase();
|
|
70
|
+
let out = "";
|
|
71
|
+
for (const char of folded) {
|
|
72
|
+
const mapped = CYRILLIC_TO_LATIN[char];
|
|
73
|
+
if (mapped !== undefined)
|
|
74
|
+
out += mapped;
|
|
75
|
+
else if (/[a-z0-9]/.test(char))
|
|
76
|
+
out += char;
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
/** {@link translitKey} without its vowels — the key two spellings of one
|
|
81
|
+
* borrowed name agree on. */
|
|
82
|
+
export function consonantKey(text) {
|
|
83
|
+
let out = "";
|
|
84
|
+
for (const char of translitKey(text))
|
|
85
|
+
if (!VOWELS.has(char))
|
|
86
|
+
out += char;
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
/** The words of a caption, split on everything that is not a letter or a
|
|
90
|
+
* digit — so `Land Rover` is reachable by "rover" in either script. */
|
|
91
|
+
function words(text) {
|
|
92
|
+
return text.split(/[^\p{L}\p{N}]+/u).filter((word) => word.length > 0);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Does `candidate` answer the typed `needle`?
|
|
96
|
+
*
|
|
97
|
+
* Prefix, not substring: a dictionary of makes searched by substring puts
|
|
98
|
+
* `Great Wall` under `all`. Every word of the candidate is a starting point,
|
|
99
|
+
* and both keys are tried — see the module note.
|
|
100
|
+
*/
|
|
101
|
+
export function translitPrefixMatch(needle, candidate) {
|
|
102
|
+
const key = translitKey(needle);
|
|
103
|
+
if (key.length === 0)
|
|
104
|
+
return true;
|
|
105
|
+
const skeleton = consonantKey(needle);
|
|
106
|
+
for (const word of [candidate, ...words(candidate)]) {
|
|
107
|
+
const wordKey = translitKey(word);
|
|
108
|
+
if (wordKey.startsWith(key))
|
|
109
|
+
return true;
|
|
110
|
+
if (skeleton.length > 0 && consonantKey(word).startsWith(skeleton))
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=translit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"translit.js","sourceRoot":"","sources":["../../src/state/translit.ts"],"names":[],"mappings":"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"}
|
package/llms.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @stapel/search-react 0.
|
|
1
|
+
# @stapel/search-react 0.23.0
|
|
2
2
|
|
|
3
3
|
Headless React flow pair for stapel-search (contract >=0.14 <0.15) — business + state, zero visual opinion.
|
|
4
4
|
Built on @stapel/core: typed client + StapelApiError envelope, auth token refresh,
|
|
@@ -85,6 +85,8 @@ const { tracked } = useTracked();
|
|
|
85
85
|
- search.location-summary → <LocationSummaryLine> [everywhere|placed|narrowed|long-place|wide] demo/LocationSummaryLine.demo.tsx
|
|
86
86
|
- search.page → <SearchPage> [desktop|phone|unreadable-link] demo/SearchPage.demo.tsx
|
|
87
87
|
- search.page-size-select → <PageSizeSelect> [ladder|off-ladder] demo/PageSizeSelect.demo.tsx
|
|
88
|
+
- search.partition-chips → <PartitionChips> [parent|child] demo/PartitionChips.demo.tsx
|
|
89
|
+
- search.popular-values → <PopularValues> [desktop|narrow-column] demo/PopularValues.demo.tsx
|
|
88
90
|
- search.range-filter-row → <RangeFilterRow> [untouched|applied] demo/RangeFilterRow.demo.tsx
|
|
89
91
|
- search.ranking-pane → <RankingDisclosurePane> [desktop|phone] demo/RankingDisclosurePane.demo.tsx
|
|
90
92
|
- search.result-card → <SearchResultCard> [gallery|promoted|unresolved|plain] demo/SearchResultCard.demo.tsx
|
package/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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.
|
|
4
|
+
"version": "0.23.0",
|
|
5
5
|
"backend": {
|
|
6
6
|
"module": "stapel-search",
|
|
7
7
|
"contract": ">=0.14 <0.15"
|
|
@@ -573,6 +573,31 @@
|
|
|
573
573
|
],
|
|
574
574
|
"source": "demo/PageSizeSelect.demo.tsx"
|
|
575
575
|
},
|
|
576
|
+
{
|
|
577
|
+
"id": "search.partition-chips",
|
|
578
|
+
"title": "Partition chips",
|
|
579
|
+
"description": "A single-select row of the parent plus its children, where the first chip is the parent itself rather than a way of clearing the others — searching the parent IS the union of its children. It is a real radiogroup with roving tabindex: Tab reaches the row once and lands on the chosen chip, the arrow keys move along it and choose as they go. A row of aria-pressed toggles would announce the opposite — independent switches, with no reason why pressing one released another.",
|
|
580
|
+
"component": "PartitionChips",
|
|
581
|
+
"variants": [
|
|
582
|
+
"parent",
|
|
583
|
+
"child"
|
|
584
|
+
],
|
|
585
|
+
"source": "demo/PartitionChips.demo.tsx"
|
|
586
|
+
},
|
|
587
|
+
{
|
|
588
|
+
"id": "search.popular-values",
|
|
589
|
+
"title": "Popular values",
|
|
590
|
+
"description": "The head of one facet as a multi-column list of value + count, each line applying that filter. The numbers are the answer's own drill-down counts — the same ones the checkbox rows carry — so a value cannot read 802 here and 93 in the panel. Values with no evidence behind them are dropped rather than printed with a blank where the number belongs: the block IS the numbers. Whether a phone has room for it is the page's decision and arrives as the `hidden` prop, never as a media query inside.",
|
|
591
|
+
"component": "PopularValues",
|
|
592
|
+
"tokens": [
|
|
593
|
+
"text-muted"
|
|
594
|
+
],
|
|
595
|
+
"variants": [
|
|
596
|
+
"desktop",
|
|
597
|
+
"narrow-column"
|
|
598
|
+
],
|
|
599
|
+
"source": "demo/PopularValues.demo.tsx"
|
|
600
|
+
},
|
|
576
601
|
{
|
|
577
602
|
"id": "search.range-filter-row",
|
|
578
603
|
"title": "Numeric range filter",
|
|
@@ -756,9 +781,14 @@
|
|
|
756
781
|
"search.empty.up_a_level",
|
|
757
782
|
"search.empty.widen_radius",
|
|
758
783
|
"search.error.unknown",
|
|
784
|
+
"search.facets.all_filters",
|
|
759
785
|
"search.facets.approximate",
|
|
760
786
|
"search.facets.clear",
|
|
761
787
|
"search.facets.clear_all",
|
|
788
|
+
"search.facets.dictionary_any",
|
|
789
|
+
"search.facets.dictionary_chosen",
|
|
790
|
+
"search.facets.dictionary_empty",
|
|
791
|
+
"search.facets.dictionary_search",
|
|
762
792
|
"search.facets.drill_down_hint",
|
|
763
793
|
"search.facets.empty",
|
|
764
794
|
"search.facets.load_failed",
|
|
@@ -767,7 +797,9 @@
|
|
|
767
797
|
"search.facets.match_count.one",
|
|
768
798
|
"search.facets.match_count.other",
|
|
769
799
|
"search.facets.not_counted",
|
|
800
|
+
"search.facets.popular_all",
|
|
770
801
|
"search.facets.range_apply",
|
|
802
|
+
"search.facets.range_bounds",
|
|
771
803
|
"search.facets.range_clear",
|
|
772
804
|
"search.facets.range_from",
|
|
773
805
|
"search.facets.range_from_aria",
|
|
@@ -815,6 +847,8 @@
|
|
|
815
847
|
"search.nav.ranking",
|
|
816
848
|
"search.nav.ranking.short",
|
|
817
849
|
"search.nav.results",
|
|
850
|
+
"search.partition.all",
|
|
851
|
+
"search.partition.label",
|
|
818
852
|
"search.range.price",
|
|
819
853
|
"search.ranking.applies_to",
|
|
820
854
|
"search.ranking.empty",
|
|
@@ -880,6 +914,7 @@
|
|
|
880
914
|
"FILTER_PREFIX",
|
|
881
915
|
"FacetPanel",
|
|
882
916
|
"RANGE_FEATURE_TYPES",
|
|
917
|
+
"RANGE_PICKER_MAX_VALUES",
|
|
883
918
|
"RANGE_PREFIX",
|
|
884
919
|
"RankingDisclosure",
|
|
885
920
|
"SEARCH_BACKEND_UNAVAILABLE",
|
|
@@ -909,6 +944,7 @@
|
|
|
909
944
|
"buildFacetGroups",
|
|
910
945
|
"buildRangeGroups",
|
|
911
946
|
"clearFilters",
|
|
947
|
+
"consonantKey",
|
|
912
948
|
"coreRangeLabelKey",
|
|
913
949
|
"countIsEstimate",
|
|
914
950
|
"countKind",
|
|
@@ -918,6 +954,8 @@
|
|
|
918
954
|
"degradationAudience",
|
|
919
955
|
"degradationMessageKey",
|
|
920
956
|
"explainSearchError",
|
|
957
|
+
"facetGroupHasEvidence",
|
|
958
|
+
"facetGroupIsDrawable",
|
|
921
959
|
"facetOptionLabel",
|
|
922
960
|
"isCountNuanceOnly",
|
|
923
961
|
"isFacetableFeature",
|
|
@@ -925,6 +963,7 @@
|
|
|
925
963
|
"isRangeUsable",
|
|
926
964
|
"navEntries",
|
|
927
965
|
"offerableCategories",
|
|
966
|
+
"orderFacetGroupsBySchema",
|
|
928
967
|
"ownsParam",
|
|
929
968
|
"parseDegradations",
|
|
930
969
|
"parseSearchState",
|
|
@@ -939,6 +978,8 @@
|
|
|
939
978
|
"setRangeValue",
|
|
940
979
|
"suggestTerms",
|
|
941
980
|
"toggleFilterValue",
|
|
981
|
+
"translitKey",
|
|
982
|
+
"translitPrefixMatch",
|
|
942
983
|
"useAppliedSort",
|
|
943
984
|
"useFacetPanel",
|
|
944
985
|
"useHostFacetLabels",
|
|
@@ -961,6 +1002,9 @@
|
|
|
961
1002
|
"FacetGroup",
|
|
962
1003
|
"FacetLabelRequest",
|
|
963
1004
|
"FacetLabelResolver",
|
|
1005
|
+
"FacetLabelSource",
|
|
1006
|
+
"FacetLabels",
|
|
1007
|
+
"FacetLabelsMap",
|
|
964
1008
|
"FacetMeta",
|
|
965
1009
|
"FacetOption",
|
|
966
1010
|
"FacetPanelBag",
|
package/nav-manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stapel/search-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.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 incl. the facetability rule and the
|
|
55
|
+
"name": "index — the headless pair (client + hooks + URL codec + facet model incl. the facetability rule, the label-source order, the across-alphabets prefix matcher, the SCHEMA-order comparator that puts a category's required axes first and the bounded-integer picker list): no antd, no react-router. Raised 12 → 12.5 KB for the rail-order comparator, the shared drawability predicate (one rule for the rail and the chip row) and the picker bounds",
|
|
56
56
|
"path": "dist/index.js",
|
|
57
|
-
"limit": "
|
|
57
|
+
"limit": "12.5 KB"
|
|
58
58
|
},
|
|
59
59
|
{
|
|
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 + the rail's evidence-ranked disclosure groups, panel search and sticky count/clear footer, results incl. the view switch, the card photo GALLERY as a SkinCarousel strip, the empty state's derived exits, 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 + the rail's evidence-ranked disclosure groups, panel search and sticky count/clear footer, results incl. the view switch, the card photo GALLERY as a SkinCarousel strip, the empty state's derived exits, ranking, the dictionary control for a vocabulary facet, the popular-values block, the partition row in both its chip and segmented variants, the select-style dictionary FIELD the desktop rail opens, the from/to pickers a bounded integer axis draws and the rail's own scrollbar sheet) must stay out of the main bundle. Raised 24.5 → 25.5 KB for those four controls",
|
|
61
61
|
"path": "dist/default/index.js",
|
|
62
|
-
"limit": "
|
|
62
|
+
"limit": "25.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",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
{
|
|
70
70
|
"name": "i18n/ru — the Russian bundle is opt-in, not carried by the main entry",
|
|
71
71
|
"path": "dist/i18n/ru.js",
|
|
72
|
-
"limit": "4.
|
|
72
|
+
"limit": "4.75 KB"
|
|
73
73
|
},
|
|
74
74
|
{
|
|
75
75
|
"name": "i18n/es — the Spanish bundle is opt-in, not carried by the main entry",
|
|
@@ -115,8 +115,8 @@
|
|
|
115
115
|
"size-limit": "^11.2.0",
|
|
116
116
|
"typescript": "^5.8.3",
|
|
117
117
|
"vitest": "^3.2.4",
|
|
118
|
-
"@stapel/attributes-react": "^0.
|
|
119
|
-
"@stapel/core": "^0.
|
|
118
|
+
"@stapel/attributes-react": "^0.15.0",
|
|
119
|
+
"@stapel/core": "^0.25.0",
|
|
120
120
|
"@stapel/image": "^0.4.2",
|
|
121
121
|
"@stapel/showcase": "^0.3.0",
|
|
122
122
|
"@stapel/tokens": "^0.7.0",
|
package/src/api/types.ts
CHANGED
|
@@ -13,10 +13,49 @@ import type { components } from "./generated/schema.js";
|
|
|
13
13
|
/** The generated schema table — the one source of truth for wire shapes. */
|
|
14
14
|
export type Schemas = components["schemas"];
|
|
15
15
|
|
|
16
|
-
/**
|
|
17
|
-
*
|
|
18
|
-
|
|
16
|
+
/**
|
|
17
|
+
* One slug's captions: the GROUP's own name, and the words for its values.
|
|
18
|
+
*
|
|
19
|
+
* `label` is the group heading the server resolved from the category's
|
|
20
|
+
* feature definition, in the request's own language. It is the only source
|
|
21
|
+
* that always exists — `categoryFeatures` is an optional slot a live
|
|
22
|
+
* classified board never filled, and without it every heading in the panel
|
|
23
|
+
* was the raw index slug. `null` is the server saying it has no name for the
|
|
24
|
+
* slug either; ABSENT is a server too old to send one, and both read the same
|
|
25
|
+
* way here (fall through to the schema).
|
|
26
|
+
*
|
|
27
|
+
* WHAT THE GENERATOR LOST: the field is newer than this pair's pinned
|
|
28
|
+
* `schema.json`, so it is declared here as optional-and-nullable rather than
|
|
29
|
+
* regenerated into a shape a deployed older server does not send.
|
|
30
|
+
*/
|
|
31
|
+
export type FacetLabels = Schemas["FacetLabels"] & {
|
|
32
|
+
readonly label?: string | null;
|
|
33
|
+
/**
|
|
34
|
+
* Whether {@link label} is a translation KEY rather than a caption, the way
|
|
35
|
+
* `translatable` says it for the values.
|
|
36
|
+
*
|
|
37
|
+
* Measured on a live answer (`category=141/151`, 2026-09-04): the server
|
|
38
|
+
* sends it beside every label — a make caption with
|
|
39
|
+
* `label_translatable: false`, a condition caption with `true` — and the
|
|
40
|
+
* describe it. Declared, not consumed: this pair passes the group heading
|
|
41
|
+
* through its translator either way, and a caption that is not a key comes
|
|
42
|
+
* back unchanged (`translate()` keeps a key it cannot resolve). Typed so a
|
|
43
|
+
* fixture captured from the wire is not a type error.
|
|
44
|
+
*/
|
|
45
|
+
readonly label_translatable?: boolean;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/** `facet_labels` as a whole: `{slug: {label, translatable, values}}`. */
|
|
49
|
+
export type FacetLabelsMap = Readonly<Record<string, FacetLabels>>;
|
|
50
|
+
|
|
51
|
+
/** `GET /query` 200 — the whole envelope. Corrected in two places: see
|
|
52
|
+
* {@link FacetMeta} and {@link FacetLabels}. */
|
|
53
|
+
export type SearchResponse = Omit<
|
|
54
|
+
Schemas["SearchResponse"],
|
|
55
|
+
"facet_meta" | "facet_labels"
|
|
56
|
+
> & {
|
|
19
57
|
readonly facet_meta: FacetMeta;
|
|
58
|
+
readonly facet_labels: FacetLabelsMap;
|
|
20
59
|
};
|
|
21
60
|
|
|
22
61
|
/** One result row. `promoted` is present on EVERY item under EVERY sort — a
|