@stapel/search-react 0.3.1 → 0.7.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 +180 -0
- package/MODULE.md +43 -4
- package/README.md +1 -1
- package/dist/api/generated/schema.d.ts +5 -1
- package/dist/api/generated/schema.d.ts.map +1 -1
- package/dist/default/DegradationNotice.d.ts +34 -0
- package/dist/default/DegradationNotice.d.ts.map +1 -1
- package/dist/default/DegradationNotice.js +28 -6
- package/dist/default/DegradationNotice.js.map +1 -1
- package/dist/default/FacetPanelPane.d.ts +54 -3
- package/dist/default/FacetPanelPane.d.ts.map +1 -1
- package/dist/default/FacetPanelPane.js +119 -19
- package/dist/default/FacetPanelPane.js.map +1 -1
- package/dist/default/LanguageSelect.d.ts +9 -0
- package/dist/default/LanguageSelect.d.ts.map +1 -0
- package/dist/default/LanguageSelect.js +72 -0
- package/dist/default/LanguageSelect.js.map +1 -0
- package/dist/default/PageSizeSelect.d.ts +29 -0
- package/dist/default/PageSizeSelect.d.ts.map +1 -0
- package/dist/default/PageSizeSelect.js +25 -0
- package/dist/default/PageSizeSelect.js.map +1 -0
- package/dist/default/RangeFilterRow.d.ts +12 -0
- package/dist/default/RangeFilterRow.d.ts.map +1 -0
- package/dist/default/RangeFilterRow.js +75 -0
- package/dist/default/RangeFilterRow.js.map +1 -0
- package/dist/default/RankingDisclosurePane.d.ts +5 -0
- package/dist/default/RankingDisclosurePane.d.ts.map +1 -1
- package/dist/default/RankingDisclosurePane.js +15 -15
- package/dist/default/RankingDisclosurePane.js.map +1 -1
- package/dist/default/SearchBox.d.ts +13 -0
- package/dist/default/SearchBox.d.ts.map +1 -0
- package/dist/default/SearchBox.js +67 -0
- package/dist/default/SearchBox.js.map +1 -0
- package/dist/default/SearchPage.d.ts +58 -16
- package/dist/default/SearchPage.d.ts.map +1 -1
- package/dist/default/SearchPage.js +155 -6
- package/dist/default/SearchPage.js.map +1 -1
- package/dist/default/SearchResultCard.d.ts +19 -18
- package/dist/default/SearchResultCard.d.ts.map +1 -1
- package/dist/default/SearchResultCard.js +178 -5
- package/dist/default/SearchResultCard.js.map +1 -1
- package/dist/default/SearchResultsPane.d.ts +54 -3
- package/dist/default/SearchResultsPane.d.ts.map +1 -1
- package/dist/default/SearchResultsPane.js +45 -25
- package/dist/default/SearchResultsPane.js.map +1 -1
- package/dist/default/SortSelect.d.ts +10 -2
- package/dist/default/SortSelect.d.ts.map +1 -1
- package/dist/default/SortSelect.js +27 -24
- package/dist/default/SortSelect.js.map +1 -1
- package/dist/default/UrlIssueNotice.d.ts.map +1 -1
- package/dist/default/UrlIssueNotice.js +19 -1
- package/dist/default/UrlIssueNotice.js.map +1 -1
- package/dist/default/index.d.ts +33 -16
- package/dist/default/index.d.ts.map +1 -1
- package/dist/default/index.js +26 -12
- package/dist/default/index.js.map +1 -1
- package/dist/default/sortLabels.d.ts +5 -0
- package/dist/default/sortLabels.d.ts.map +1 -0
- package/dist/default/sortLabels.js +32 -0
- package/dist/default/sortLabels.js.map +1 -0
- package/dist/headless/FacetPanel.d.ts +17 -0
- package/dist/headless/FacetPanel.d.ts.map +1 -1
- package/dist/headless/FacetPanel.js +23 -2
- package/dist/headless/FacetPanel.js.map +1 -1
- package/dist/headless/SearchResults.d.ts +20 -3
- package/dist/headless/SearchResults.d.ts.map +1 -1
- package/dist/headless/SearchResults.js +3 -1
- package/dist/headless/SearchResults.js.map +1 -1
- package/dist/headless/useAppliedCount.d.ts +8 -0
- package/dist/headless/useAppliedCount.d.ts.map +1 -0
- package/dist/headless/useAppliedCount.js +30 -0
- package/dist/headless/useAppliedCount.js.map +1 -0
- package/dist/headless/useScorerNames.d.ts +4 -0
- package/dist/headless/useScorerNames.d.ts.map +1 -0
- package/dist/headless/useScorerNames.js +34 -0
- package/dist/headless/useScorerNames.js.map +1 -0
- package/dist/headless/useSearchBox.d.ts +44 -0
- package/dist/headless/useSearchBox.d.ts.map +1 -0
- package/dist/headless/useSearchBox.js +116 -0
- package/dist/headless/useSearchBox.js.map +1 -0
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +32 -4
- package/dist/i18n/es.js.map +1 -1
- package/dist/i18n/keys.d.ts +46 -2
- package/dist/i18n/keys.d.ts.map +1 -1
- package/dist/i18n/keys.js +86 -6
- package/dist/i18n/keys.js.map +1 -1
- package/dist/i18n/ru.d.ts.map +1 -1
- package/dist/i18n/ru.js +38 -4
- package/dist/i18n/ru.js.map +1 -1
- package/dist/index.d.ts +9 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/model/queries.d.ts +26 -2
- package/dist/model/queries.d.ts.map +1 -1
- package/dist/model/queries.js +38 -1
- package/dist/model/queries.js.map +1 -1
- package/dist/state/degradations.d.ts +41 -0
- package/dist/state/degradations.d.ts.map +1 -1
- package/dist/state/degradations.js +41 -0
- package/dist/state/degradations.js.map +1 -1
- package/dist/state/limits.d.ts +44 -0
- package/dist/state/limits.d.ts.map +1 -0
- package/dist/state/limits.js +44 -0
- package/dist/state/limits.js.map +1 -0
- package/dist/state/ranges.d.ts +52 -0
- package/dist/state/ranges.d.ts.map +1 -0
- package/dist/state/ranges.js +108 -0
- package/dist/state/ranges.js.map +1 -0
- package/llms.txt +16 -5
- package/manifest.json +245 -22
- package/nav-manifest.json +1 -1
- package/package.json +15 -10
- package/src/analytics/generated/events.json +1 -1
- package/src/api/generated/schema.ts +5 -1
- package/src/default/DegradationNotice.tsx +84 -14
- package/src/default/FacetPanelPane.tsx +343 -64
- package/src/default/LanguageSelect.tsx +93 -0
- package/src/default/PageSizeSelect.tsx +80 -0
- package/src/default/RangeFilterRow.tsx +148 -0
- package/src/default/RankingDisclosurePane.tsx +67 -35
- package/src/default/SearchBox.tsx +130 -0
- package/src/default/SearchPage.tsx +356 -41
- package/src/default/SearchResultCard.tsx +231 -24
- package/src/default/SearchResultsPane.tsx +147 -76
- package/src/default/SortSelect.tsx +50 -37
- package/src/default/UrlIssueNotice.tsx +21 -3
- package/src/default/index.ts +42 -16
- package/src/default/sortLabels.ts +34 -0
- package/src/headless/FacetPanel.tsx +30 -2
- package/src/headless/SearchResults.tsx +32 -4
- package/src/headless/useAppliedCount.ts +41 -0
- package/src/headless/useScorerNames.ts +36 -0
- package/src/headless/useSearchBox.ts +189 -0
- package/src/i18n/es.ts +39 -4
- package/src/i18n/keys.ts +97 -6
- package/src/i18n/ru.ts +45 -4
- package/src/index.ts +30 -3
- package/src/model/queries.ts +51 -1
- package/src/state/degradations.ts +60 -0
- package/src/state/limits.ts +50 -0
- package/src/state/ranges.ts +144 -0
- package/dist/default/ErrorAlert.d.ts +0 -9
- package/dist/default/ErrorAlert.d.ts.map +0 -1
- package/dist/default/ErrorAlert.js +0 -27
- package/dist/default/ErrorAlert.js.map +0 -1
- package/dist/default/theme.d.ts +0 -24
- package/dist/default/theme.d.ts.map +0 -1
- package/dist/default/theme.js +0 -39
- package/dist/default/theme.js.map +0 -1
- package/src/default/ErrorAlert.tsx +0 -46
- package/src/default/theme.tsx +0 -66
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.
|
|
4
|
+
"version": "0.7.0",
|
|
5
5
|
"backend": {
|
|
6
6
|
"module": "stapel-search",
|
|
7
|
-
"contract": ">=0.
|
|
7
|
+
"contract": ">=0.2 <0.3"
|
|
8
8
|
},
|
|
9
9
|
"layers": [
|
|
10
10
|
"api",
|
|
@@ -70,6 +70,17 @@
|
|
|
70
70
|
"query",
|
|
71
71
|
"{params}"
|
|
72
72
|
]
|
|
73
|
+
},
|
|
74
|
+
"useSuggest": {
|
|
75
|
+
"kind": "query",
|
|
76
|
+
"operation": "suggest",
|
|
77
|
+
"queryKey": [
|
|
78
|
+
"search",
|
|
79
|
+
"suggest",
|
|
80
|
+
"{type}",
|
|
81
|
+
"{q}",
|
|
82
|
+
"{…}"
|
|
83
|
+
]
|
|
73
84
|
}
|
|
74
85
|
},
|
|
75
86
|
"errors": {
|
|
@@ -393,47 +404,207 @@
|
|
|
393
404
|
},
|
|
394
405
|
"demos": [
|
|
395
406
|
{
|
|
396
|
-
"id": "search.
|
|
397
|
-
"title": "
|
|
398
|
-
"description": "
|
|
399
|
-
"component": "
|
|
407
|
+
"id": "search.box",
|
|
408
|
+
"title": "Search box",
|
|
409
|
+
"description": "Types into a draft and searches after a pause, so the address bar does not chase the keyboard; the URL still wins whenever it moves on its own, so the box can never show a word the results are not about. Capped at the server's own MAX_QUERY_CHARS, which makes 'query too long' a refusal this control cannot cause.",
|
|
410
|
+
"component": "SearchBox",
|
|
400
411
|
"tokens": [
|
|
401
412
|
"surface-raised"
|
|
402
413
|
],
|
|
403
414
|
"variants": [
|
|
404
|
-
"
|
|
415
|
+
"empty",
|
|
416
|
+
"typed"
|
|
405
417
|
],
|
|
406
|
-
"source": "demo/
|
|
418
|
+
"source": "demo/SearchBox.demo.tsx"
|
|
407
419
|
},
|
|
408
420
|
{
|
|
409
|
-
"id": "search.
|
|
410
|
-
"title": "
|
|
411
|
-
"description": "
|
|
412
|
-
"component": "
|
|
421
|
+
"id": "search.degradation-notice",
|
|
422
|
+
"title": "Degradation notice",
|
|
423
|
+
"description": "What the engine could not do for THIS query, in words: the typo pass it skipped, the facet counts it sampled rather than counted, the scorer it could not evaluate. An unknown degradation still renders — with its raw literal — because a build that predates a limitation should say 'the engine reported something we have no wording for', not nothing.",
|
|
424
|
+
"component": "DegradationNotice",
|
|
413
425
|
"tokens": [
|
|
414
426
|
"surface-raised"
|
|
415
427
|
],
|
|
416
428
|
"variants": [
|
|
417
|
-
"
|
|
429
|
+
"banner",
|
|
430
|
+
"inline"
|
|
418
431
|
],
|
|
419
|
-
"source": "demo/
|
|
432
|
+
"source": "demo/DegradationNotice.demo.tsx"
|
|
420
433
|
},
|
|
421
434
|
{
|
|
422
|
-
"id": "search.
|
|
423
|
-
"title": "
|
|
424
|
-
"description": "
|
|
425
|
-
"component": "
|
|
435
|
+
"id": "search.facet-panel-pane",
|
|
436
|
+
"title": "Filter panel",
|
|
437
|
+
"description": "The drill-down facet panel with every count beside its option — including the options you have not chosen, which is what makes them drill-down rather than naive — plus the server's honesty flags said in words: 'these counts are approximate' and the slugs it did not count at all, whose options read 'not counted' and never a zero that looks like an answer.",
|
|
438
|
+
"component": "FacetPanelPane",
|
|
426
439
|
"covers": [
|
|
440
|
+
"FacetPanel"
|
|
441
|
+
],
|
|
442
|
+
"tokens": [
|
|
443
|
+
"surface-raised"
|
|
444
|
+
],
|
|
445
|
+
"variants": [
|
|
446
|
+
"open-search",
|
|
447
|
+
"narrowed"
|
|
448
|
+
],
|
|
449
|
+
"source": "demo/FacetPanelPane.demo.tsx"
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
"id": "search.filter-sheet",
|
|
453
|
+
"title": "Filter sheet",
|
|
454
|
+
"description": "The filters as a phone actually meets them: a bottom sheet over the results, with the drill-down counts beside every option, a Clear all for the two constraints the link arrived with, and a footer that commits by saying how many results the current choices lead to rather than a bare 'Apply'.",
|
|
455
|
+
"component": "SearchPage",
|
|
456
|
+
"tokens": [
|
|
457
|
+
"surface-raised"
|
|
458
|
+
],
|
|
459
|
+
"variants": [
|
|
460
|
+
"open"
|
|
461
|
+
],
|
|
462
|
+
"source": "demo/FilterSheet.demo.tsx"
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
"id": "search.language-select",
|
|
466
|
+
"title": "Language filter",
|
|
467
|
+
"description": "The languages a deployment indexes, each named the way THIS reader would name it. With nothing offered and nothing applied the control does not render at all: a select whose only option is 'any' is a dead control, and the panel is thinner without it.",
|
|
468
|
+
"component": "LanguageSelect",
|
|
469
|
+
"tokens": [
|
|
470
|
+
"surface-raised"
|
|
471
|
+
],
|
|
472
|
+
"variants": [
|
|
473
|
+
"offered",
|
|
474
|
+
"from-a-link"
|
|
475
|
+
],
|
|
476
|
+
"source": "demo/LanguageSelect.demo.tsx"
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
"id": "search.page",
|
|
480
|
+
"title": "Search page",
|
|
481
|
+
"description": "The composed /s screen: the query box bound to setText, the filter panel (category and location slots, numeric ranges, drill-down facets), the sort and page-size toolbar, and one keyset page of cards with the DSA promoted marking explained in words rather than in a tooltip.",
|
|
482
|
+
"component": "SearchPage",
|
|
483
|
+
"covers": [
|
|
484
|
+
"SearchStateProvider",
|
|
427
485
|
"SearchProvider",
|
|
428
|
-
"
|
|
486
|
+
"SearchResults",
|
|
487
|
+
"FacetPanel"
|
|
488
|
+
],
|
|
489
|
+
"tokens": [
|
|
490
|
+
"surface-raised",
|
|
491
|
+
"warning-bg"
|
|
492
|
+
],
|
|
493
|
+
"variants": [
|
|
494
|
+
"desktop",
|
|
495
|
+
"phone",
|
|
496
|
+
"unreadable-link"
|
|
497
|
+
],
|
|
498
|
+
"source": "demo/SearchPage.demo.tsx"
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
"id": "search.page-size-select",
|
|
502
|
+
"title": "Page size control",
|
|
503
|
+
"description": "A short ladder around the server's DEFAULT_PAGE_SIZE. Choosing a size REPLACES the history entry rather than pushing one: a page size is a preference, not a step through the results, and Back should still undo the last filter.",
|
|
504
|
+
"component": "PageSizeSelect",
|
|
505
|
+
"tokens": [
|
|
506
|
+
"surface-raised"
|
|
507
|
+
],
|
|
508
|
+
"variants": [
|
|
509
|
+
"ladder",
|
|
510
|
+
"off-ladder"
|
|
429
511
|
],
|
|
512
|
+
"source": "demo/PageSizeSelect.demo.tsx"
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
"id": "search.range-filter-row",
|
|
516
|
+
"title": "Numeric range filter",
|
|
517
|
+
"description": "A range row per numeric feature of the category schema, plus any slug the URL already constrains — because a constraint that is active must always have a control that removes it, even when the schema no longer explains it. Bounds and step come from the schema; the unit is shown in the field's accessible name.",
|
|
518
|
+
"component": "RangeFilterRow",
|
|
519
|
+
"tokens": [
|
|
520
|
+
"surface-raised"
|
|
521
|
+
],
|
|
522
|
+
"variants": [
|
|
523
|
+
"untouched",
|
|
524
|
+
"applied"
|
|
525
|
+
],
|
|
526
|
+
"source": "demo/RangeFilterRow.demo.tsx"
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
"id": "search.ranking-pane",
|
|
530
|
+
"title": "Ranking disclosure",
|
|
531
|
+
"description": "The statutory ranking page in the default skin: every scorer with its weight, the sorts it applies to, and — for a scorer the deployment's engine cannot evaluate — the reason it is inactive, so the disclosure describes the ranking that actually ran rather than the one that was configured.",
|
|
532
|
+
"component": "RankingDisclosurePane",
|
|
533
|
+
"covers": [
|
|
534
|
+
"RankingDisclosure"
|
|
535
|
+
],
|
|
536
|
+
"tokens": [
|
|
537
|
+
"surface-raised"
|
|
538
|
+
],
|
|
539
|
+
"variants": [
|
|
540
|
+
"desktop",
|
|
541
|
+
"phone"
|
|
542
|
+
],
|
|
543
|
+
"source": "demo/RankingDisclosurePane.demo.tsx"
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
"id": "search.result-card",
|
|
547
|
+
"title": "Result card",
|
|
548
|
+
"description": "The default card a doc type gets for free: a 4:3 photo well drawn before the network answers (via @stapel/image, so a dead URL is a named placeholder rather than a torn-page icon), the title, the price with its currency, the location and distance — and the promoted marking painted from a token role rather than an antd preset, with its explanation as ordinary text beneath it.",
|
|
549
|
+
"component": "SearchResultCard",
|
|
550
|
+
"tokens": [
|
|
551
|
+
"warning-bg",
|
|
552
|
+
"warning-on",
|
|
553
|
+
"warning-border",
|
|
554
|
+
"surface-sunken"
|
|
555
|
+
],
|
|
556
|
+
"variants": [
|
|
557
|
+
"promoted",
|
|
558
|
+
"plain"
|
|
559
|
+
],
|
|
560
|
+
"source": "demo/SearchResultCard.demo.tsx"
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
"id": "search.results-pane",
|
|
564
|
+
"title": "Results pane",
|
|
565
|
+
"description": "One keyset page in the default skin: a counted heading (a capped count reads 'N+', never a flat number the server did not promise), the degradation banner, an auto-fill card grid, and a pager that is ABSENT when there is nothing to page rather than disabled with its reason in a tooltip.",
|
|
566
|
+
"component": "SearchResultsPane",
|
|
567
|
+
"covers": [
|
|
568
|
+
"SearchResults"
|
|
569
|
+
],
|
|
570
|
+
"tokens": [
|
|
571
|
+
"surface-raised",
|
|
572
|
+
"warning-bg"
|
|
573
|
+
],
|
|
574
|
+
"variants": [
|
|
575
|
+
"desktop",
|
|
576
|
+
"phone",
|
|
577
|
+
"empty"
|
|
578
|
+
],
|
|
579
|
+
"source": "demo/SearchResultsPane.demo.tsx"
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
"id": "search.sort-select",
|
|
583
|
+
"title": "Sort control",
|
|
584
|
+
"description": "The five shipped sorts, plus whatever unknown sort a shared link already carries — offered rather than silently reset, because resetting it would rewrite the meaning of somebody's link on load and the server is the one entitled to refuse an unknown sort by name.",
|
|
585
|
+
"component": "SortSelect",
|
|
586
|
+
"tokens": [
|
|
587
|
+
"surface-raised"
|
|
588
|
+
],
|
|
589
|
+
"variants": [
|
|
590
|
+
"no-centre",
|
|
591
|
+
"with-centre"
|
|
592
|
+
],
|
|
593
|
+
"source": "demo/SortSelect.demo.tsx"
|
|
594
|
+
},
|
|
595
|
+
{
|
|
596
|
+
"id": "search.url-issue-notice",
|
|
597
|
+
"title": "Unreadable link notice",
|
|
598
|
+
"description": "The parameters a shared link carried that the codec could not read, named one by one. The alternative is not 'no notice' but a quietly WIDER search than the one that was shared, which is the difference between a page that is wrong and a page that says which part of the link it lost.",
|
|
599
|
+
"component": "UrlIssueNotice",
|
|
430
600
|
"tokens": [
|
|
431
601
|
"surface-raised"
|
|
432
602
|
],
|
|
433
603
|
"variants": [
|
|
434
|
-
"
|
|
604
|
+
"two-issues",
|
|
605
|
+
"one-issue"
|
|
435
606
|
],
|
|
436
|
-
"source": "demo/
|
|
607
|
+
"source": "demo/UrlIssueNotice.demo.tsx"
|
|
437
608
|
}
|
|
438
609
|
],
|
|
439
610
|
"i18nKeys": [
|
|
@@ -491,6 +662,14 @@
|
|
|
491
662
|
"error.500.internal",
|
|
492
663
|
"error.503.mandate_unavailable",
|
|
493
664
|
"error.503.search_backend_unavailable",
|
|
665
|
+
"search.box.clear",
|
|
666
|
+
"search.box.label",
|
|
667
|
+
"search.box.placeholder",
|
|
668
|
+
"search.box.submit",
|
|
669
|
+
"search.box.suggestions",
|
|
670
|
+
"search.category.clear",
|
|
671
|
+
"search.category.current",
|
|
672
|
+
"search.category.title",
|
|
494
673
|
"search.degraded.category_rollup",
|
|
495
674
|
"search.degraded.exact_facet_counts",
|
|
496
675
|
"search.degraded.exact_total",
|
|
@@ -509,15 +688,34 @@
|
|
|
509
688
|
"search.facets.loading",
|
|
510
689
|
"search.facets.not_counted",
|
|
511
690
|
"search.facets.range_apply",
|
|
691
|
+
"search.facets.range_clear",
|
|
512
692
|
"search.facets.range_from",
|
|
693
|
+
"search.facets.range_from_aria",
|
|
694
|
+
"search.facets.range_invalid",
|
|
513
695
|
"search.facets.range_to",
|
|
696
|
+
"search.facets.range_to_aria",
|
|
514
697
|
"search.facets.skipped",
|
|
515
698
|
"search.facets.title",
|
|
699
|
+
"search.filters.apply",
|
|
700
|
+
"search.filters.dismiss",
|
|
701
|
+
"search.filters.open",
|
|
702
|
+
"search.filters.show_count",
|
|
703
|
+
"search.filters.show_count.one",
|
|
704
|
+
"search.filters.show_count.other",
|
|
705
|
+
"search.filters.show_count_at_least",
|
|
706
|
+
"search.filters.show_count_at_least.one",
|
|
707
|
+
"search.filters.show_count_at_least.other",
|
|
516
708
|
"search.geo.box",
|
|
517
709
|
"search.geo.center",
|
|
518
710
|
"search.geo.clear",
|
|
519
711
|
"search.geo.radius_km",
|
|
712
|
+
"search.geo.radius_label",
|
|
520
713
|
"search.geo.title",
|
|
714
|
+
"search.language.any",
|
|
715
|
+
"search.language.label",
|
|
716
|
+
"search.limit.from_link",
|
|
717
|
+
"search.limit.label",
|
|
718
|
+
"search.limit.option",
|
|
521
719
|
"search.ranking.applies_to",
|
|
522
720
|
"search.ranking.empty",
|
|
523
721
|
"search.ranking.inactive",
|
|
@@ -534,11 +732,15 @@
|
|
|
534
732
|
"search.results.count_approximate",
|
|
535
733
|
"search.results.count_approximate.one",
|
|
536
734
|
"search.results.count_approximate.other",
|
|
735
|
+
"search.results.count_at_least",
|
|
736
|
+
"search.results.count_at_least.one",
|
|
737
|
+
"search.results.count_at_least.other",
|
|
537
738
|
"search.results.count_exact",
|
|
538
739
|
"search.results.count_exact.one",
|
|
539
740
|
"search.results.count_exact.other",
|
|
540
741
|
"search.results.distance_km",
|
|
541
742
|
"search.results.empty",
|
|
743
|
+
"search.results.image_alt",
|
|
542
744
|
"search.results.load_failed",
|
|
543
745
|
"search.results.loading",
|
|
544
746
|
"search.results.next",
|
|
@@ -568,29 +770,42 @@
|
|
|
568
770
|
"runtime": [
|
|
569
771
|
"FILTER_PREFIX",
|
|
570
772
|
"FacetPanel",
|
|
773
|
+
"RANGE_FEATURE_TYPES",
|
|
571
774
|
"RANGE_PREFIX",
|
|
572
775
|
"RankingDisclosure",
|
|
573
776
|
"SEARCH_BACKEND_UNAVAILABLE",
|
|
777
|
+
"SEARCH_BOX_DEBOUNCE_MS",
|
|
778
|
+
"SEARCH_BOX_SUGGEST_DEBOUNCE_MS",
|
|
779
|
+
"SEARCH_DEFAULT_PAGE_SIZE",
|
|
574
780
|
"SEARCH_ERRORS",
|
|
575
781
|
"SEARCH_ERROR_CODES",
|
|
576
782
|
"SEARCH_I18N_KEYS",
|
|
577
783
|
"SEARCH_I18N_PLURAL_KEYS",
|
|
784
|
+
"SEARCH_MAX_PAGE_SIZE",
|
|
578
785
|
"SEARCH_PARAM",
|
|
786
|
+
"SEARCH_QUERY_MAX_CHARS",
|
|
579
787
|
"SEARCH_SORTS",
|
|
580
788
|
"SEARCH_WINDOW_EXCEEDED",
|
|
789
|
+
"SUGGEST_MAX_LIMIT",
|
|
790
|
+
"SUGGEST_MIN_CHARS",
|
|
581
791
|
"SearchProvider",
|
|
582
792
|
"SearchResults",
|
|
583
793
|
"SearchRuntimeContext",
|
|
584
794
|
"SearchStateProvider",
|
|
585
795
|
"activeFilterCount",
|
|
586
796
|
"buildFacetGroups",
|
|
797
|
+
"buildRangeGroups",
|
|
587
798
|
"clearFilters",
|
|
588
799
|
"countIsEstimate",
|
|
800
|
+
"countKind",
|
|
589
801
|
"createSearchApi",
|
|
590
802
|
"createSearchRuntime",
|
|
591
803
|
"degradationMessageKey",
|
|
592
804
|
"explainSearchError",
|
|
593
805
|
"facetOptionLabel",
|
|
806
|
+
"isCountNuanceOnly",
|
|
807
|
+
"isRangeFeature",
|
|
808
|
+
"isRangeUsable",
|
|
594
809
|
"navEntries",
|
|
595
810
|
"ownsParam",
|
|
596
811
|
"parseDegradations",
|
|
@@ -605,16 +820,20 @@
|
|
|
605
820
|
"setRangeValue",
|
|
606
821
|
"toggleFilterValue",
|
|
607
822
|
"useAppliedSort",
|
|
823
|
+
"useFacetPanel",
|
|
608
824
|
"useRankingDisclosure",
|
|
609
825
|
"useSearchAnalytics",
|
|
610
826
|
"useSearchApi",
|
|
827
|
+
"useSearchBox",
|
|
611
828
|
"useSearchQuery",
|
|
612
829
|
"useSearchRuntime",
|
|
613
830
|
"useSearchState",
|
|
831
|
+
"useSuggest",
|
|
614
832
|
"writeSearchState"
|
|
615
833
|
],
|
|
616
834
|
"types": [
|
|
617
835
|
"BuildFacetGroupsInput",
|
|
836
|
+
"BuildRangeGroupsInput",
|
|
618
837
|
"CreateSearchRuntimeOptions",
|
|
619
838
|
"FacetGroup",
|
|
620
839
|
"FacetMeta",
|
|
@@ -623,12 +842,15 @@
|
|
|
623
842
|
"FacetSelection",
|
|
624
843
|
"ParseSearchStateOptions",
|
|
625
844
|
"ParsedSearchState",
|
|
845
|
+
"RangeGroup",
|
|
626
846
|
"RankingDisclosureBag",
|
|
627
847
|
"RankingResponse",
|
|
628
848
|
"Remediation",
|
|
629
849
|
"Schemas",
|
|
630
850
|
"Scorer",
|
|
631
851
|
"SearchApi",
|
|
852
|
+
"SearchBoxBag",
|
|
853
|
+
"SearchCountKind",
|
|
632
854
|
"SearchDegradation",
|
|
633
855
|
"SearchDegradationKind",
|
|
634
856
|
"SearchErrorCode",
|
|
@@ -652,7 +874,8 @@
|
|
|
652
874
|
"SearchStatePatch",
|
|
653
875
|
"SearchStateProviderProps",
|
|
654
876
|
"SuggestParams",
|
|
655
|
-
"SuggestResponse"
|
|
877
|
+
"SuggestResponse",
|
|
878
|
+
"UseSearchBoxOptions"
|
|
656
879
|
]
|
|
657
880
|
}
|
|
658
881
|
}
|
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.7.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,9 +57,9 @@
|
|
|
57
57
|
"limit": "10 KB"
|
|
58
58
|
},
|
|
59
59
|
{
|
|
60
|
-
"name": "default — the antd skin (
|
|
60
|
+
"name": "default — the antd skin (query box + typeahead, filters incl. ranges/geo/category slots, results, ranking) must stay out of the main bundle",
|
|
61
61
|
"path": "dist/default/index.js",
|
|
62
|
-
"limit": "
|
|
62
|
+
"limit": "16 KB"
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
65
|
"name": "router — the react-router binding is opt-in; the main entry must never pull a router",
|
|
@@ -79,14 +79,18 @@
|
|
|
79
79
|
],
|
|
80
80
|
"peerDependencies": {
|
|
81
81
|
"@stapel/attributes-react": ">=0.1.0",
|
|
82
|
-
"@stapel/core": ">=0.
|
|
83
|
-
"@stapel/
|
|
82
|
+
"@stapel/core": ">=0.18.1 <1.0.0",
|
|
83
|
+
"@stapel/image": ">=0.3.0",
|
|
84
|
+
"@stapel/tokens-antd": ">=0.7.0",
|
|
84
85
|
"@tanstack/react-query": "^5.0.0",
|
|
85
86
|
"antd": ">=5.20.0 <7",
|
|
86
87
|
"react": ">=19",
|
|
87
88
|
"react-router": ">=7.0.0 <8"
|
|
88
89
|
},
|
|
89
90
|
"peerDependenciesMeta": {
|
|
91
|
+
"@stapel/image": {
|
|
92
|
+
"optional": true
|
|
93
|
+
},
|
|
90
94
|
"@stapel/tokens-antd": {
|
|
91
95
|
"optional": true
|
|
92
96
|
},
|
|
@@ -111,11 +115,12 @@
|
|
|
111
115
|
"size-limit": "^11.2.0",
|
|
112
116
|
"typescript": "^5.8.3",
|
|
113
117
|
"vitest": "^3.2.4",
|
|
114
|
-
"@stapel/
|
|
115
|
-
"@stapel/
|
|
116
|
-
"@stapel/
|
|
117
|
-
"@stapel/tokens": "^0.5.
|
|
118
|
-
"@stapel/
|
|
118
|
+
"@stapel/image": "^0.4.1",
|
|
119
|
+
"@stapel/showcase": "^0.3.0",
|
|
120
|
+
"@stapel/core": "^0.18.1",
|
|
121
|
+
"@stapel/tokens": "^0.5.1",
|
|
122
|
+
"@stapel/attributes-react": "^0.3.1",
|
|
123
|
+
"@stapel/tokens-antd": "^0.7.0"
|
|
119
124
|
},
|
|
120
125
|
"engines": {
|
|
121
126
|
"node": ">=22"
|
|
@@ -213,7 +213,11 @@ export interface components {
|
|
|
213
213
|
prev_anchor: string | null;
|
|
214
214
|
has_next: boolean;
|
|
215
215
|
has_prev: boolean;
|
|
216
|
-
count
|
|
216
|
+
/** @description How many documents match. NEVER 0 beside a non-empty items[] — the answer may not claim fewer matches than the page shows. `null` means the engine cannot say, and is rendered as no count at all. */
|
|
217
|
+
count: number | null;
|
|
218
|
+
/** @description True when `count` is a floor: at least this many match, possibly more (a capped count, a window-truncated engine answer). Render 'N+', never 'N'. */
|
|
219
|
+
count_is_lower_bound: boolean;
|
|
220
|
+
/** @description True when `count` is exact for THIS answer — equivalent to `count is not null and not count_is_lower_bound`. Per answer, not per engine: an engine without a guaranteed exact total still counts a small candidate set exactly. */
|
|
217
221
|
exact_total: boolean;
|
|
218
222
|
/** @description What the configured engine could not do for this query. */
|
|
219
223
|
degraded: string[];
|
|
@@ -9,41 +9,111 @@
|
|
|
9
9
|
* An `unknown` degradation still renders — with the raw literal, because a
|
|
10
10
|
* build that predates a new limitation should say "the engine reported
|
|
11
11
|
* something we have no wording for: X", not nothing at all.
|
|
12
|
+
*
|
|
13
|
+
* ── Two deliberate ways NOT to shout ───────────────────────────────────────
|
|
14
|
+
*
|
|
15
|
+
* 1. **`exact_total` alone never raises a banner.** It is a count NUANCE, not
|
|
16
|
+
* a failed search: the rows are right, and the single consequence — that
|
|
17
|
+
* the total is a floor — is already spoken by the count as "N+". A warning
|
|
18
|
+
* box over a perfectly good result page teaches the reader that the page
|
|
19
|
+
* is broken, and a banner that cries wolf on every landing page is a
|
|
20
|
+
* banner nobody reads on the day `category_rollup` appears in it. Beside
|
|
21
|
+
* any other degradation it renders normally, because the list is then
|
|
22
|
+
* describing an answer that really is degraded. See
|
|
23
|
+
* {@link isCountNuanceOnly}.
|
|
24
|
+
* 2. **`variant`.** A catalogue page wants the banner; a landing page that
|
|
25
|
+
* shows six cards under a hero has no room for a warning box and passes
|
|
26
|
+
* `"inline"` (one quiet line) or `"off"`. `"off"` is a decision the
|
|
27
|
+
* CONTAINER makes about ITS surface — the notice stays the default, and
|
|
28
|
+
* nothing here silences a degradation for everyone.
|
|
12
29
|
*/
|
|
13
30
|
import type { ReactElement } from "react";
|
|
14
|
-
import { Alert, Typography } from "antd";
|
|
31
|
+
import { Alert, Flex, Typography } from "antd";
|
|
32
|
+
import { fontSize, spacing } from "@stapel/tokens";
|
|
15
33
|
import { useT } from "@stapel/core";
|
|
16
34
|
import type { SearchDegradation } from "../api/types.js";
|
|
35
|
+
import { isCountNuanceOnly } from "../state/degradations.js";
|
|
17
36
|
import { SEARCH_I18N_KEYS } from "../i18n/keys.js";
|
|
18
37
|
|
|
38
|
+
/**
|
|
39
|
+
* How loudly a surface says what the engine could not do.
|
|
40
|
+
*
|
|
41
|
+
* - `"banner"` (default) — an antd warning `Alert` with one line per item.
|
|
42
|
+
* - `"inline"` — the same sentences as quiet secondary text, no box.
|
|
43
|
+
* - `"off"` — nothing. For a surface where the notice does not belong at all;
|
|
44
|
+
* the container that turns it off owns saying so somewhere else.
|
|
45
|
+
*/
|
|
46
|
+
export type DegradationNoticeVariant = "banner" | "inline" | "off";
|
|
47
|
+
|
|
19
48
|
export interface DegradationNoticeProps {
|
|
20
49
|
readonly degradations: readonly SearchDegradation[];
|
|
50
|
+
readonly variant?: DegradationNoticeVariant;
|
|
51
|
+
/**
|
|
52
|
+
* Names a `scorer:` degradation's slug, when something on the page knows
|
|
53
|
+
* one. `<SearchResultsPane>` passes the ranking disclosure's own names
|
|
54
|
+
* (`useScorerNames`); with no answer the slug is printed, which is what a
|
|
55
|
+
* registry identifier is worth on its own.
|
|
56
|
+
*/
|
|
57
|
+
readonly scorerName?: (slug: string) => string | undefined;
|
|
21
58
|
}
|
|
22
59
|
|
|
23
60
|
export function DegradationNotice(
|
|
24
61
|
props: DegradationNoticeProps
|
|
25
62
|
): ReactElement | null {
|
|
26
63
|
const t = useT();
|
|
64
|
+
const variant: DegradationNoticeVariant = props.variant ?? "banner";
|
|
65
|
+
if (variant === "off") return null;
|
|
27
66
|
if (props.degradations.length === 0) return null;
|
|
67
|
+
// A count nuance is not a degraded search — the count already says "N+".
|
|
68
|
+
if (isCountNuanceOnly(props.degradations)) return null;
|
|
69
|
+
|
|
70
|
+
const say = (degradation: SearchDegradation): string => {
|
|
71
|
+
const slug = degradation.scorer;
|
|
72
|
+
return t(degradation.messageKey, {
|
|
73
|
+
scorer:
|
|
74
|
+
slug === undefined
|
|
75
|
+
? ""
|
|
76
|
+
: (props.scorerName?.(slug) ?? slug),
|
|
77
|
+
raw: degradation.raw,
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// NOT `type="secondary"` in the banner: antd paints a warning Alert in the
|
|
82
|
+
// theme's warning tint, and grey body text on it measured under 3:1 in the
|
|
83
|
+
// visual pass. Inside a coloured box the readable colour is the box's own
|
|
84
|
+
// text colour, which is what a plain `<Typography.Text>` inherits.
|
|
85
|
+
const lines = props.degradations.map((degradation) => (
|
|
86
|
+
<li key={degradation.raw} data-degradation={degradation.raw}>
|
|
87
|
+
<Typography.Text>{say(degradation)}</Typography.Text>
|
|
88
|
+
</li>
|
|
89
|
+
));
|
|
90
|
+
|
|
91
|
+
if (variant === "inline") {
|
|
92
|
+
return (
|
|
93
|
+
<Flex vertical gap={spacing[1]} data-testid="search-degraded" data-variant="inline">
|
|
94
|
+
<Typography.Text type="secondary" style={{ fontSize: fontSize.xs.fontSize }}>
|
|
95
|
+
{t(SEARCH_I18N_KEYS.degradedTitle)}
|
|
96
|
+
</Typography.Text>
|
|
97
|
+
<ul style={{ margin: 0, paddingInlineStart: spacing[5], fontSize: fontSize.xs.fontSize }}>
|
|
98
|
+
{props.degradations.map((degradation) => (
|
|
99
|
+
<li key={degradation.raw} data-degradation={degradation.raw}>
|
|
100
|
+
<Typography.Text type="secondary">{say(degradation)}</Typography.Text>
|
|
101
|
+
</li>
|
|
102
|
+
))}
|
|
103
|
+
</ul>
|
|
104
|
+
</Flex>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
28
108
|
return (
|
|
29
109
|
<Alert
|
|
30
110
|
type="warning"
|
|
31
111
|
showIcon
|
|
32
112
|
data-testid="search-degraded"
|
|
33
|
-
|
|
113
|
+
data-variant="banner"
|
|
114
|
+
title={t(SEARCH_I18N_KEYS.degradedTitle)}
|
|
34
115
|
description={
|
|
35
|
-
<ul style={{ margin: 0, paddingInlineStart:
|
|
36
|
-
{props.degradations.map((degradation) => (
|
|
37
|
-
<li key={degradation.raw} data-degradation={degradation.raw}>
|
|
38
|
-
<Typography.Text type="secondary">
|
|
39
|
-
{t(degradation.messageKey, {
|
|
40
|
-
scorer: degradation.scorer ?? "",
|
|
41
|
-
raw: degradation.raw,
|
|
42
|
-
})}
|
|
43
|
-
</Typography.Text>
|
|
44
|
-
</li>
|
|
45
|
-
))}
|
|
46
|
-
</ul>
|
|
116
|
+
<ul style={{ margin: 0, paddingInlineStart: spacing[5] }}>{lines}</ul>
|
|
47
117
|
}
|
|
48
118
|
/>
|
|
49
119
|
);
|