@stapel/search-react 0.34.0 → 0.35.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.
@@ -312,6 +312,14 @@ export interface SearchPageProps extends ThemeModeProp, ParseSearchStateOptions
312
312
  readonly adapter: SearchParamsAdapter;
313
313
  readonly renderCard?: SearchCardRenderer;
314
314
  readonly categoryFeatures?: readonly FeatureDef[];
315
+ /**
316
+ * The schema is ON ITS WAY — the third state `categoryFeatures` does not
317
+ * have. Handed straight to the filter panel, which holds the box it already
318
+ * reserves rather than drawing a rail it is about to re-shape and re-order.
319
+ * See {@link FacetPanelPaneProps.categoryFeaturesPending} for what was
320
+ * measured without it (p41).
321
+ */
322
+ readonly categoryFeaturesPending?: boolean;
315
323
  readonly locale?: string;
316
324
  /**
317
325
  * Name the facet values neither the answer nor the category schema names —
@@ -431,6 +439,22 @@ export interface SearchPageProps extends ThemeModeProp, ParseSearchStateOptions
431
439
  * sense that matters and not a decoration bolted on top.
432
440
  */
433
441
  readonly filtersHeader?: ReactNode;
442
+ /**
443
+ * The block-size that slot will END UP at, declared before it has anything
444
+ * in it — a number in CSS pixels or any length (`"96px"`, `"6rem"`).
445
+ *
446
+ * For the host whose header is a SEPARATE read from the search: a category
447
+ * page's partition row is two chained catalogue requests behind the answer,
448
+ * so the rail draws its groups first and the row drops in over them a beat
449
+ * later, pushing every filter under it down (p41). The band is in flow from
450
+ * the first frame with this set — an empty box of the right height, then the
451
+ * control inside it — and the pair never guesses the number, because the
452
+ * height of a control it does not own is not the pair's to know.
453
+ *
454
+ * A FLOOR, like every other reservation here: a header taller than the
455
+ * declared number still takes the room it needs.
456
+ */
457
+ readonly filtersHeaderReserve?: number | string;
434
458
  /**
435
459
  * The row ABOVE the chips and the results — where `<LocationSummaryLine>`
436
460
  * goes on the phone SERP.
@@ -679,6 +703,8 @@ interface SearchPageBodyProps {
679
703
  readonly dictionaryMode?: "field" | "inline" | "sheet";
680
704
  readonly visibleGroups?: number | null;
681
705
  readonly categoryFeatures?: readonly FeatureDef[];
706
+ readonly categoryFeaturesPending?: boolean;
707
+ readonly filtersHeaderReserve?: number | string;
682
708
  readonly renderEmptyExits?: () => ReactNode;
683
709
  readonly locale?: string;
684
710
  readonly pinnedFacets?: readonly string[];
@@ -787,7 +813,13 @@ function SearchPageBody(props: SearchPageBodyProps): ReactElement {
787
813
  // have called that column empty.
788
814
  ...(facets.ranges !== undefined ? { ranges: facets.ranges } : {}),
789
815
  });
816
+ /* A schema still in flight makes every reading below provisional — see
817
+ `FacetPanelPaneProps.categoryFeaturesPending`. "Nothing to filter by" in
818
+ particular: the groups in hand are the ones the panel is about to redraw,
819
+ and a column closed on them would open again a beat later. */
820
+ const schemaPending = props.categoryFeaturesPending === true;
790
821
  const filtersEmpty =
822
+ !schemaPending &&
791
823
  facets.state.status === "ready" &&
792
824
  facets.state.data.length === 0 &&
793
825
  // `withheld` (groups the server counted and held back for covering too
@@ -806,11 +838,28 @@ function SearchPageBody(props: SearchPageBodyProps): ReactElement {
806
838
  props.renderCategoryFilter === undefined)) &&
807
839
  state.lang === undefined &&
808
840
  (props.languages ?? []).length === 0;
809
- const showFilters = filtersHeader !== undefined || !filtersEmpty;
841
+ const showFilters =
842
+ filtersHeader !== undefined ||
843
+ props.filtersHeaderReserve !== undefined ||
844
+ !filtersEmpty;
810
845
 
811
846
  const panel = (
812
847
  <Flex vertical gap={spacing[4]}>
813
- {filtersHeader}
848
+ {/* THE HOST'S BAND, IN FLOW FROM THE FIRST FRAME (p41). The wrapper is
849
+ drawn whenever there is something to draw OR a height to hold, so a
850
+ header that arrives with a second read lands INTO its box rather than
851
+ inserting one above the groups. See `filtersHeaderReserve`. */}
852
+ {(filtersHeader !== undefined ||
853
+ props.filtersHeaderReserve !== undefined) && (
854
+ <div
855
+ data-testid="search-filters-header"
856
+ {...(props.filtersHeaderReserve !== undefined
857
+ ? { style: { minBlockSize: props.filtersHeaderReserve } }
858
+ : {})}
859
+ >
860
+ {filtersHeader}
861
+ </div>
862
+ )}
814
863
  {/* The facet panel is skipped entirely when the only thing it would draw
815
864
  is its own empty state and the column is open for the host's control
816
865
  alone — one empty-state illustration under a working filter is still
@@ -841,6 +890,9 @@ function SearchPageBody(props: SearchPageBodyProps): ReactElement {
841
890
  : 16
842
891
  }
843
892
  {...(categoryFeatures !== undefined ? { categoryFeatures } : {})}
893
+ {...(props.categoryFeaturesPending !== undefined
894
+ ? { categoryFeaturesPending: props.categoryFeaturesPending }
895
+ : {})}
844
896
  {...(props.renderEmptyExits !== undefined
845
897
  ? { renderEmptyExits: props.renderEmptyExits }
846
898
  : {})}
@@ -1094,6 +1146,7 @@ export function SearchPage(props: SearchPageProps): ReactElement {
1094
1146
  adapter,
1095
1147
  renderCard,
1096
1148
  categoryFeatures,
1149
+ categoryFeaturesPending,
1097
1150
  locale,
1098
1151
  resolveFacetLabels,
1099
1152
  searchBox,
@@ -1106,6 +1159,7 @@ export function SearchPage(props: SearchPageProps): ReactElement {
1106
1159
  geoOffer,
1107
1160
  footer,
1108
1161
  filtersHeader,
1162
+ filtersHeaderReserve,
1109
1163
  resultsHeader,
1110
1164
  resultsLead,
1111
1165
  categoryFilter,
@@ -1149,6 +1203,9 @@ export function SearchPage(props: SearchPageProps): ReactElement {
1149
1203
  {...(visibleGroups !== undefined ? { visibleGroups } : {})}
1150
1204
  {...(pinnedFacets !== undefined ? { pinnedFacets } : {})}
1151
1205
  {...(categoryFeatures !== undefined ? { categoryFeatures } : {})}
1206
+ {...(categoryFeaturesPending !== undefined
1207
+ ? { categoryFeaturesPending }
1208
+ : {})}
1152
1209
  {...(locale !== undefined ? { locale } : {})}
1153
1210
  {...(resolveFacetLabels !== undefined ? { resolveFacetLabels } : {})}
1154
1211
  {...(searchBox !== undefined ? { searchBox } : {})}
@@ -1160,6 +1217,7 @@ export function SearchPage(props: SearchPageProps): ReactElement {
1160
1217
  {...(skippedNotice !== undefined ? { skippedNotice } : {})}
1161
1218
  {...(footer !== undefined ? { footer } : {})}
1162
1219
  {...(filtersHeader !== undefined ? { filtersHeader } : {})}
1220
+ {...(filtersHeaderReserve !== undefined ? { filtersHeaderReserve } : {})}
1163
1221
  {...(resultsHeader !== undefined ? { resultsHeader } : {})}
1164
1222
  {...(resultsLead !== undefined ? { resultsLead } : {})}
1165
1223
  {...(categoryFilter !== undefined ? { categoryFilter } : {})}
@@ -122,10 +122,15 @@ export type { LocationSummaryLineProps } from "./LocationSummaryLine.js";
122
122
  export {
123
123
  FacetGroupControl,
124
124
  facetGroupIsEmptyHeading,
125
+ facetGroupReservedHeight,
125
126
  facetGroupShape,
126
127
  facetOptionNodes,
127
128
  isDictionaryFacet,
128
129
  FACET_DICTIONARY_THRESHOLD,
130
+ FACET_HEADING_HEIGHT,
131
+ FACET_OPTION_ROW_HEIGHT,
132
+ FACET_PILLS_PER_ROW,
133
+ FACET_PILL_ROW_HEIGHT,
129
134
  FACET_SHEET_PAGE,
130
135
  FACET_VISIBLE_OPTIONS,
131
136
  } from "./FacetGroupControl.js";