@stapel/search-react 0.12.0 → 0.14.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 +109 -0
- package/MODULE.md +7 -3
- package/dist/default/LocationSummaryLine.d.ts +19 -0
- package/dist/default/LocationSummaryLine.d.ts.map +1 -1
- package/dist/default/LocationSummaryLine.js +111 -11
- package/dist/default/LocationSummaryLine.js.map +1 -1
- package/dist/default/SearchResultCard.d.ts.map +1 -1
- package/dist/default/SearchResultCard.js +83 -37
- package/dist/default/SearchResultCard.js.map +1 -1
- package/dist/default/cardPhotos.d.ts +65 -0
- package/dist/default/cardPhotos.d.ts.map +1 -0
- package/dist/default/cardPhotos.js +127 -0
- package/dist/default/cardPhotos.js.map +1 -0
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +3 -0
- package/dist/i18n/es.js.map +1 -1
- package/dist/i18n/keys.d.ts +10 -0
- package/dist/i18n/keys.d.ts.map +1 -1
- package/dist/i18n/keys.js +13 -0
- package/dist/i18n/keys.js.map +1 -1
- package/dist/i18n/ru.d.ts.map +1 -1
- package/dist/i18n/ru.js +3 -0
- package/dist/i18n/ru.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/model/runtime.d.ts +40 -3
- package/dist/model/runtime.d.ts.map +1 -1
- package/dist/model/runtime.js +2 -1
- package/dist/model/runtime.js.map +1 -1
- package/llms.txt +3 -3
- package/manifest.json +14 -3
- package/nav-manifest.json +1 -1
- package/package.json +5 -5
- package/src/analytics/generated/events.json +1 -1
- package/src/default/LocationSummaryLine.tsx +150 -22
- package/src/default/SearchResultCard.tsx +157 -71
- package/src/default/cardPhotos.ts +195 -0
- package/src/i18n/es.ts +3 -0
- package/src/i18n/keys.ts +13 -0
- package/src/i18n/ru.ts +3 -0
- package/src/index.ts +1 -0
- package/src/model/runtime.ts +43 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,114 @@
|
|
|
1
1
|
# @stapel/search-react
|
|
2
2
|
|
|
3
|
+
## 0.14.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 396e32e: The phone SERP's location row is one compact line again, and stops overlapping itself.
|
|
8
|
+
|
|
9
|
+
Measured on a live 390×844 SERP, in both themes, on every result page — the
|
|
10
|
+
category listing, the text search and every filter slice:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
{"kind":"clipped-left","t":"A chosen place on the map","x":-4}
|
|
14
|
+
{"kind":"overlap","a":"· Within 25 km","b":"Filters","px":43}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
On screen that read as "…hosen place on the map · Within 25 kmlters", with the
|
|
18
|
+
red active-filter plaque floating in the top-right corner of the page attached
|
|
19
|
+
to nothing. At 1440 it was clean, which is why nothing before this caught it.
|
|
20
|
+
|
|
21
|
+
**Three causes, none of them the flex row itself.**
|
|
22
|
+
|
|
23
|
+
1. An antd `<Button>` CENTRES its content. `minWidth: 0` let the left half
|
|
24
|
+
shrink and nothing clipped what was inside it, so the label overflowed its
|
|
25
|
+
box symmetrically — off the left edge of the screen at `x = -4` and 43px
|
|
26
|
+
across the word "Filters" at the other end. A shrunk box with no `overflow`
|
|
27
|
+
is not a truncation, it is an overlap.
|
|
28
|
+
2. Nothing declared which end may shrink. Both were `1 1 auto`, so a long place
|
|
29
|
+
name took width from a word that must never lose any.
|
|
30
|
+
3. The count was an antd `<Badge count>` — an absolutely positioned `sup` hung
|
|
31
|
+
off the top-right CORNER of what it wraps. At the trailing edge of a
|
|
32
|
+
full-width row that puts it outside the row entirely.
|
|
33
|
+
|
|
34
|
+
**Now:** `place · radius` left, `Filters` right, on one line. The left half is
|
|
35
|
+
the only one that grows or shrinks and it truncates with an ellipsis (`display:
|
|
36
|
+
block` on the label is load-bearing — `text-overflow` does nothing on a flex
|
|
37
|
+
box, which is how the first attempt cut the name mid-glyph); the right half is
|
|
38
|
+
`flex: 0 0 auto`; the count is a pill IN the flow beside the word it counts
|
|
39
|
+
for. The place and the radius now share ONE truncating label, so the radius can
|
|
40
|
+
no longer travel past the label's end on its own.
|
|
41
|
+
|
|
42
|
+
The rules that had to reach inside the button ship as a hoisted stylesheet
|
|
43
|
+
(`locationLineCss`, the pattern `<ListingCard>` and `<SkinCarousel>` use),
|
|
44
|
+
since a descendant rule is not expressible as an inline style.
|
|
45
|
+
|
|
46
|
+
`search-location-filters-badge` is now the trailing group rather than an antd
|
|
47
|
+
`<Badge>`; the count carries `data-testid="search-location-filters-count"`, and
|
|
48
|
+
the truncating label `data-testid="search-location-label"`.
|
|
49
|
+
|
|
50
|
+
A new skin demo variant, `long-place`, photographs the measured case: a place
|
|
51
|
+
name a geocoder really returns, at 390px, with a radius and a count on the same
|
|
52
|
+
line.
|
|
53
|
+
|
|
54
|
+
## 0.13.0
|
|
55
|
+
|
|
56
|
+
### Minor Changes
|
|
57
|
+
|
|
58
|
+
- e76ab28: The default SERP card draws the photos — the whole gallery, not one, and not none.
|
|
59
|
+
|
|
60
|
+
`<SearchResultCard>` read `card.image` as an object with a `url` key and fell
|
|
61
|
+
back to `card.image_url`. Neither is a shape this fleet emits, so **every
|
|
62
|
+
consumer that did not pass its own `renderCard` got a card with no photo at
|
|
63
|
+
all**:
|
|
64
|
+
|
|
65
|
+
- `image_url` is a convention nothing in the fleet writes. It was declared in
|
|
66
|
+
`GENERIC_CARD_FIELDS`, drawn in the demos from a data URI, and never once
|
|
67
|
+
served by a backend.
|
|
68
|
+
- `card.image` IS emitted — `stapel-classified`'s search projection stores it —
|
|
69
|
+
and it is a plain `<type>/<hash>` CDN reference **string**, not an object.
|
|
70
|
+
- the one rich shape that exists (chat's subject card, which serves the same
|
|
71
|
+
CDN render descriptor its attachments carry) has `ref` + `variants[]` and no
|
|
72
|
+
top-level `url`, so the `"url" in rich` guard rejected it too.
|
|
73
|
+
|
|
74
|
+
**What the card reads now** (`default/cardPhotos.ts`, unit-tested as data):
|
|
75
|
+
`images[]` first — the whole seller-ordered gallery `stapel-classified` 0.7.0
|
|
76
|
+
projects, capped by its `CARD_IMAGES_LIMIT` — with the singular `image` as the
|
|
77
|
+
fallback for a doc type that never grew a list. Never both: `image` IS
|
|
78
|
+
`images[0]`, so reading the singular after the list would draw the first photo
|
|
79
|
+
twice. Each entry may be a CDN reference, a URL the doc type stored itself (a
|
|
80
|
+
reference is `<type>/<hash>`: no scheme, no leading slash, so the two are told
|
|
81
|
+
apart by shape and never by a guess), or a render descriptor, whose whole
|
|
82
|
+
variant ladder survives so `<Image>` still has tiers to choose between.
|
|
83
|
+
|
|
84
|
+
**How a reference becomes a picture: a new `resolveImage` seam** on
|
|
85
|
+
`createSearchRuntime`, the same seam `@stapel/listings-react` states and the
|
|
86
|
+
same function a container passes to both. No contract in this fleet resolves a
|
|
87
|
+
stranger's reference — stapel-cdn's `file/exists/` is owner-scoped — so the
|
|
88
|
+
deployment hands its own knowledge in once rather than having a library invent
|
|
89
|
+
a URL convention nobody agreed to.
|
|
90
|
+
|
|
91
|
+
**A gallery is a strip.** Two or more photos render as a `<SkinCarousel>` with
|
|
92
|
+
the peek and the position indicator (one photo gets neither: a sliver of a next
|
|
93
|
+
slide is an affordance for something that is not there). The strip is a
|
|
94
|
+
**sibling** of the card's anchor, never a child — a horizontal swipe that ends
|
|
95
|
+
inside an `<a>` can be delivered as a click, which is the defect that makes
|
|
96
|
+
phone galleries unusable.
|
|
97
|
+
|
|
98
|
+
**Three honest answers about a photo, drawn as three different things.** A card
|
|
99
|
+
with no photo field reserves nothing (a text corpus is not a gallery with holes
|
|
100
|
+
in it); a card whose references nothing resolved draws the well and _says_ the
|
|
101
|
+
photo is unavailable — that is what an unwired `resolveImage` looks like, and a
|
|
102
|
+
sentence gets it fixed where an empty grey box does not; anything else is the
|
|
103
|
+
strip.
|
|
104
|
+
|
|
105
|
+
BREAKING for a host that stored `card.image_url`: that field is no longer read
|
|
106
|
+
(nothing in the fleet wrote it). Store the URL in `image`/`images` instead — a
|
|
107
|
+
stored URL still needs no resolver.
|
|
108
|
+
|
|
109
|
+
New i18n keys in all three bundles: `search.results.photos`,
|
|
110
|
+
`search.results.photo_alt`, `search.results.photo_unavailable`.
|
|
111
|
+
|
|
3
112
|
## 0.12.0
|
|
4
113
|
|
|
5
114
|
### Minor Changes
|
package/MODULE.md
CHANGED
|
@@ -112,9 +112,13 @@ are props, not dependencies (spec §6.2 items 1–2):
|
|
|
112
112
|
|
|
113
113
|
- **`renderCard`** — `<ListingCard>` from `@stapel/listings-react/default`. The
|
|
114
114
|
generic card here is a working default over a documented set of conventional
|
|
115
|
-
`card` fields (`title`, `price`, `currency`, `location`, `
|
|
116
|
-
`SearchItem.card` is deliberately free-form: "stored row
|
|
117
|
-
page costs one query".
|
|
115
|
+
`card` fields (`title`, `price`, `currency`, `location`, `images`, `image`,
|
|
116
|
+
`url`), because `SearchItem.card` is deliberately free-form: "stored row
|
|
117
|
+
fields, so a result page costs one query". `images` is the whole gallery in
|
|
118
|
+
seller order (stapel-classified 0.7.0) and `image` is `images[0]`; both hold
|
|
119
|
+
opaque `<type>/<hash>` CDN references, so the card draws them through the
|
|
120
|
+
runtime's `resolveImage` seam — the same seam `@stapel/listings-react`
|
|
121
|
+
states, and the same function a container passes to both.
|
|
118
122
|
- **`categoryFeatures`** — the category's feature schema, from
|
|
119
123
|
`categories-react`. The server sends `{value: count}` and no labels; the
|
|
120
124
|
captions are translation keys in that schema.
|
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
import type { ReactElement, ReactNode } from "react";
|
|
2
2
|
import type { GeoFilterSlotProps } from "./FacetPanelPane.js";
|
|
3
|
+
/** The class the row carries, for {@link locationLineCss}. */
|
|
4
|
+
export declare const LOCATION_LINE_CLASS = "stapel-search-location-line";
|
|
5
|
+
/** The class the shrinking half carries. */
|
|
6
|
+
export declare const LOCATION_LINE_WHERE_CLASS = "stapel-search-location-line-where";
|
|
7
|
+
/** The class the truncating label carries. */
|
|
8
|
+
export declare const LOCATION_LINE_LABEL_CLASS = "stapel-search-location-line-label";
|
|
9
|
+
/** The class the fixed half carries. */
|
|
10
|
+
export declare const LOCATION_LINE_END_CLASS = "stapel-search-location-line-end";
|
|
11
|
+
/** The `href` the hoisted stylesheet is deduplicated by. */
|
|
12
|
+
export declare const LOCATION_LINE_STYLE_HREF = "stapel-search-location-line";
|
|
13
|
+
/**
|
|
14
|
+
* The rules that keep the line ONE line — and that an inline style cannot
|
|
15
|
+
* reach, because they belong on the `<span>` antd wraps a button's children
|
|
16
|
+
* in. See the file header (defect C12) for what each of them stops.
|
|
17
|
+
*
|
|
18
|
+
* Static: nothing here varies per instance, so one hoisted copy serves a page
|
|
19
|
+
* in either theme.
|
|
20
|
+
*/
|
|
21
|
+
export declare function locationLineCss(): string;
|
|
3
22
|
export interface LocationSummaryLineProps {
|
|
4
23
|
/**
|
|
5
24
|
* The location control the sheet opens onto — the same slot the panel and
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocationSummaryLine.d.ts","sourceRoot":"","sources":["../../src/default/LocationSummaryLine.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"LocationSummaryLine.d.ts","sourceRoot":"","sources":["../../src/default/LocationSummaryLine.tsx"],"names":[],"mappings":"AAkEA,OAAO,KAAK,EAAiB,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAOpE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAG9D,8DAA8D;AAC9D,eAAO,MAAM,mBAAmB,gCAAgC,CAAC;AACjE,4CAA4C;AAC5C,eAAO,MAAM,yBAAyB,sCAAsC,CAAC;AAC7E,8CAA8C;AAC9C,eAAO,MAAM,yBAAyB,sCAAsC,CAAC;AAC7E,wCAAwC;AACxC,eAAO,MAAM,uBAAuB,oCAAoC,CAAC;AAEzE,4DAA4D;AAC5D,eAAO,MAAM,wBAAwB,gCAAgC,CAAC;AAEtE;;;;;;;GAOG;AACH,wBAAgB,eAAe,IAAI,MAAM,CA4BxC;AAsCD,MAAM,WAAW,wBAAwB;IACvC;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,SAAS,CAAC;IACnE;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,IAAI,CAAC;CAChC;AAyBD,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,wBAAwB,GAC9B,YAAY,CA+Gd"}
|
|
@@ -28,29 +28,129 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
28
28
|
*
|
|
29
29
|
* `<FilterChips>`'s leading chip is a 32px circle: a number inside it is a
|
|
30
30
|
* number nobody reads, so it shows a dot. This is a full-width row with a word
|
|
31
|
-
* on it, so
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
31
|
+
* on it, so it has room to say HOW MANY constraints are applied — which is the
|
|
32
|
+
* difference between "something is filtered" and "four things are, and that is
|
|
33
|
+
* why there are three results". Both read the same `activeFilters` off the URL
|
|
34
|
+
* state; neither invents a second counter.
|
|
35
|
+
*
|
|
36
|
+
* ## The row overlapped itself on a phone, and why (defect C12)
|
|
37
|
+
*
|
|
38
|
+
* Measured on a live 390px SERP, in both themes, on every result page:
|
|
39
|
+
*
|
|
40
|
+
* ```
|
|
41
|
+
* {"kind":"clipped-left","t":"A chosen place on the map","x":-4}
|
|
42
|
+
* {"kind":"overlap","a":"· Within 25 km","b":"Filters","px":43}
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* Three separate causes, and none of them was the flex row itself:
|
|
46
|
+
*
|
|
47
|
+
* 1. **An antd `<Button>` CENTRES its content.** `minWidth: 0` let the left
|
|
48
|
+
* item shrink, and nothing clipped what was inside it, so the label
|
|
49
|
+
* overflowed its box symmetrically — off the left edge of the screen at
|
|
50
|
+
* `x = -4` and 43px across the word "Filters" at the other end. A shrunk
|
|
51
|
+
* box with no `overflow` is not a truncation, it is an overlap.
|
|
52
|
+
* 2. **Nothing declared which end may shrink.** Both ends were `1 1 auto`, so
|
|
53
|
+
* a long place name took width from a word that must never lose any.
|
|
54
|
+
* 3. **The count was an antd `<Badge count>`,** which is an absolutely
|
|
55
|
+
* positioned `sup` hung off the top-right CORNER of what it wraps. At the
|
|
56
|
+
* trailing edge of a full-width row that lands it outside the row entirely
|
|
57
|
+
* — a red plaque floating in the corner of the page, attached to nothing
|
|
58
|
+
* (measured at `y = 72` for a row whose own line is at `y = 93`).
|
|
59
|
+
*
|
|
60
|
+
* So: one compact line, `place · radius` on the left and `Filters` right; the
|
|
61
|
+
* left is the only half that shrinks and it truncates with an ellipsis; the
|
|
62
|
+
* count rides IN the flow beside the word it counts for. The first two rules
|
|
63
|
+
* are what an inline style cannot reach (they belong on the wrapper antd puts
|
|
64
|
+
* around a button's children), which is why this file hoists a stylesheet the
|
|
65
|
+
* way `<ListingCard>` and `<SkinCarousel>` do.
|
|
35
66
|
*/
|
|
36
67
|
import { useState } from "react";
|
|
37
|
-
import {
|
|
68
|
+
import { Button, Flex } from "antd";
|
|
38
69
|
import { useT } from "@stapel/core";
|
|
39
|
-
import { spacing } from "@stapel/tokens";
|
|
70
|
+
import { cssVar, fontSize, radii, spacing } from "@stapel/tokens";
|
|
40
71
|
import { useSearchState } from "../headless/SearchStateProvider.js";
|
|
41
72
|
import { SEARCH_I18N_KEYS } from "../i18n/keys.js";
|
|
42
73
|
import { geoSummaryFallback } from "./FacetPanelPane.js";
|
|
43
74
|
import { GeoSheet, SUMMARY_GEO_TEST_IDS } from "./geoSheet.js";
|
|
75
|
+
/** The class the row carries, for {@link locationLineCss}. */
|
|
76
|
+
export const LOCATION_LINE_CLASS = "stapel-search-location-line";
|
|
77
|
+
/** The class the shrinking half carries. */
|
|
78
|
+
export const LOCATION_LINE_WHERE_CLASS = "stapel-search-location-line-where";
|
|
79
|
+
/** The class the truncating label carries. */
|
|
80
|
+
export const LOCATION_LINE_LABEL_CLASS = "stapel-search-location-line-label";
|
|
81
|
+
/** The class the fixed half carries. */
|
|
82
|
+
export const LOCATION_LINE_END_CLASS = "stapel-search-location-line-end";
|
|
83
|
+
/** The `href` the hoisted stylesheet is deduplicated by. */
|
|
84
|
+
export const LOCATION_LINE_STYLE_HREF = "stapel-search-location-line";
|
|
85
|
+
/**
|
|
86
|
+
* The rules that keep the line ONE line — and that an inline style cannot
|
|
87
|
+
* reach, because they belong on the `<span>` antd wraps a button's children
|
|
88
|
+
* in. See the file header (defect C12) for what each of them stops.
|
|
89
|
+
*
|
|
90
|
+
* Static: nothing here varies per instance, so one hoisted copy serves a page
|
|
91
|
+
* in either theme.
|
|
92
|
+
*/
|
|
93
|
+
export function locationLineCss() {
|
|
94
|
+
const row = `.${LOCATION_LINE_CLASS}`;
|
|
95
|
+
const where = `.${LOCATION_LINE_WHERE_CLASS}`;
|
|
96
|
+
const label = `.${LOCATION_LINE_LABEL_CLASS}`;
|
|
97
|
+
const end = `.${LOCATION_LINE_END_CLASS}`;
|
|
98
|
+
return [
|
|
99
|
+
// `min-width:0` on the row too: a flex item's default `min-width:auto`
|
|
100
|
+
// is what makes a nested flex row refuse to shrink at all.
|
|
101
|
+
`${row}{min-inline-size:0}`,
|
|
102
|
+
// The ONLY half that shrinks, and it CLIPS what it cannot show. An antd
|
|
103
|
+
// Button is already a flex row and it CENTRES its children, so without
|
|
104
|
+
// these two the box shrank and its content overflowed both edges at once
|
|
105
|
+
// — the whole defect: `x:-4` on the left, 43px over "Filters" on the
|
|
106
|
+
// right.
|
|
107
|
+
`${where}{flex:1 1 auto;min-inline-size:0;overflow:hidden;` +
|
|
108
|
+
`justify-content:flex-start}`,
|
|
109
|
+
// Whatever the button puts its children in has to be allowed to shrink;
|
|
110
|
+
// a flex item's `min-width:auto` refuses to go below its content.
|
|
111
|
+
`${where}>span{min-inline-size:0}`,
|
|
112
|
+
// `display:block` is load-bearing: `text-overflow` applies to a BLOCK
|
|
113
|
+
// container and does nothing on a flex one, so a label that had been
|
|
114
|
+
// turned into a flex box truncated with a hard cut mid-glyph instead of
|
|
115
|
+
// an ellipsis.
|
|
116
|
+
`${label}{display:block;min-inline-size:0;overflow:hidden;` +
|
|
117
|
+
`text-overflow:ellipsis;white-space:nowrap}`,
|
|
118
|
+
// The word a person is looking for never loses a pixel to a place name.
|
|
119
|
+
`${end}{flex:0 0 auto}`,
|
|
120
|
+
].join("");
|
|
121
|
+
}
|
|
44
122
|
/** The row. Both ends are text buttons, so the row reads as a line of type
|
|
45
123
|
* rather than as two controls bolted onto a results page. */
|
|
46
124
|
const ROW = { width: "100%" };
|
|
47
|
-
/**
|
|
48
|
-
*
|
|
125
|
+
/** The shrinking half. The flex rules it needs live in the hoisted sheet
|
|
126
|
+
* (they have to reach antd's own wrapper span); this is the chrome. */
|
|
49
127
|
const LOCATION = {
|
|
50
|
-
minWidth: 0,
|
|
51
128
|
paddingInline: 0,
|
|
52
129
|
textAlign: "start",
|
|
53
130
|
};
|
|
131
|
+
/** The pin, which is not allowed to shrink either — a squashed glyph is
|
|
132
|
+
* noise, and it costs 16px. */
|
|
133
|
+
const PIN = { flex: "0 0 auto", display: "inline-flex" };
|
|
134
|
+
/**
|
|
135
|
+
* The count, IN the flow.
|
|
136
|
+
*
|
|
137
|
+
* An antd `<Badge count>` is an absolutely positioned `sup` on the corner of
|
|
138
|
+
* whatever it wraps, and at the trailing edge of a full-width row that puts it
|
|
139
|
+
* outside the row: a red plaque in the corner of the page attached to nothing.
|
|
140
|
+
* A pill beside the word is the same fact, in the line it belongs to, and it
|
|
141
|
+
* takes part in the layout instead of floating over it.
|
|
142
|
+
*/
|
|
143
|
+
const COUNT = {
|
|
144
|
+
display: "inline-block",
|
|
145
|
+
minInlineSize: `${String(spacing[4])}px`,
|
|
146
|
+
paddingInline: spacing[1],
|
|
147
|
+
borderRadius: radii.full,
|
|
148
|
+
background: cssVar("brand"),
|
|
149
|
+
color: cssVar("text-on-accent"),
|
|
150
|
+
fontSize: fontSize.xs.fontSize,
|
|
151
|
+
lineHeight: `${String(spacing[4])}px`,
|
|
152
|
+
textAlign: "center",
|
|
153
|
+
};
|
|
54
154
|
/** A map pin in `currentColor` — the house convention: an inline monochrome
|
|
55
155
|
* SVG, no icon-font dependency, and it inherits the theme rather than carrying
|
|
56
156
|
* a colour. `aria-hidden` because the text beside it is the name. */
|
|
@@ -72,9 +172,9 @@ export function LocationSummaryLine(props) {
|
|
|
72
172
|
const where = geo === undefined
|
|
73
173
|
? t(SEARCH_I18N_KEYS.geoEverywhere)
|
|
74
174
|
: (props.geoLabel ?? geoSummaryFallback(geo, t));
|
|
75
|
-
return (_jsxs(_Fragment, { children: [_jsxs(Flex, { align: "center", justify: "space-between", gap: spacing[2], style: ROW, "data-testid": "search-location-summary", "data-geo": geo === undefined ? "off" : "on", children: [_jsxs(Button, { type: "link", style: LOCATION,
|
|
175
|
+
return (_jsxs(_Fragment, { children: [_jsx("style", { href: LOCATION_LINE_STYLE_HREF, precedence: "default", children: locationLineCss() }), _jsxs(Flex, { align: "center", justify: "space-between", gap: spacing[2], style: ROW, className: LOCATION_LINE_CLASS, "data-testid": "search-location-summary", "data-geo": geo === undefined ? "off" : "on", children: [_jsxs(Button, { type: "link", style: LOCATION, className: LOCATION_LINE_WHERE_CLASS, "data-testid": "search-location-open", "data-analytics": "none", "data-analytics-reason": "opening the location sheet is a read, not a flow step", onClick: () => {
|
|
76
176
|
setOpen(true);
|
|
77
|
-
}, children: [where, radius !== undefined && (_jsxs("span", { "data-testid": "search-location-radius", children: [" · ", radius] }))] }),
|
|
177
|
+
}, children: [_jsx("span", { style: PIN, children: _jsx(PinGlyph, {}) }), _jsxs("span", { className: LOCATION_LINE_LABEL_CLASS, "data-testid": "search-location-label", children: [where, radius !== undefined && (_jsxs("span", { "data-testid": "search-location-radius", children: [" · ", radius] }))] })] }), _jsxs(Flex, { align: "center", gap: spacing[1], className: LOCATION_LINE_END_CLASS, "data-testid": "search-location-filters-badge", children: [_jsx(Button, { type: "link", style: { paddingInline: 0 }, "data-testid": "search-location-filters", "data-active": activeFilters > 0 ? "true" : "false", "data-analytics": "none", "data-analytics-reason": "opening the filter sheet is a read, not a flow step", onClick: props.onOpenAll, children: t(SEARCH_I18N_KEYS.filtersShort) }), activeFilters > 0 && (_jsx("span", { style: COUNT, "data-testid": "search-location-filters-count", children: activeFilters }))] })] }), _jsx(GeoSheet, { open: open, onClose: () => {
|
|
78
178
|
setOpen(false);
|
|
79
179
|
}, testIds: SUMMARY_GEO_TEST_IDS, ...(props.renderGeoFilter !== undefined
|
|
80
180
|
? { renderGeoFilter: props.renderGeoFilter }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocationSummaryLine.js","sourceRoot":"","sources":["../../src/default/LocationSummaryLine.tsx"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"LocationSummaryLine.js","sourceRoot":"","sources":["../../src/default/LocationSummaryLine.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SearchResultCard.d.ts","sourceRoot":"","sources":["../../src/default/SearchResultCard.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SearchResultCard.d.ts","sourceRoot":"","sources":["../../src/default/SearchResultCard.tsx"],"names":[],"mappings":"AAiDA,OAAO,KAAK,EAAiB,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAKpE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAMlD,oCAAoC;AACpC,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;CAC3B;AAED,wDAAwD;AACxD,MAAM,MAAM,kBAAkB,GAAG,CAAC,IAAI,EAAE,UAAU,KAAK,SAAS,CAAC;AAQjE;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAWhD,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B,MAAM,GAAG,SAAS,CAoBpB;AA0KD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,eAAe,GAAG,YAAY,CAmFrE"}
|
|
@@ -19,11 +19,14 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
19
19
|
*
|
|
20
20
|
* ── Two things this card used to get wrong ────────────────────────────────
|
|
21
21
|
*
|
|
22
|
-
* 1. **It
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
22
|
+
* 1. **It read a photo shape nothing in this fleet emits.** `card.image` was
|
|
23
|
+
* read as an object with a `url` key and `card.image_url` as the fallback;
|
|
24
|
+
* what the fleet actually stores is a `<type>/<hash>` CDN reference, and
|
|
25
|
+
* where it does store an object that object carries `ref` + `variants[]`
|
|
26
|
+
* and no top-level `url`. So every consumer that did not pass its own
|
|
27
|
+
* `renderCard` got a card with no photo at all. `cardPhotos.ts` reads the
|
|
28
|
+
* real shapes, through the runtime's `resolveImage` seam, and the whole
|
|
29
|
+
* `images[]` gallery rather than one photo.
|
|
27
30
|
* 2. **The marking's EXPLANATION was in a `Tooltip`.** Touch has no hover, so
|
|
28
31
|
* on the device most of this traffic arrives from, the sentence the law is
|
|
29
32
|
* actually about was unreachable — the tag said "Promoted" and nothing on
|
|
@@ -47,8 +50,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
47
50
|
import { useMemo } from "react";
|
|
48
51
|
import { Card, Flex, Typography } from "antd";
|
|
49
52
|
import { Image } from "@stapel/image";
|
|
53
|
+
import { SkinCarousel } from "@stapel/tokens-antd/skin";
|
|
50
54
|
import { useFormat, useT } from "@stapel/core";
|
|
51
55
|
import { cssVar, fontSize, fontWeight, radii, spacing } from "@stapel/tokens";
|
|
56
|
+
import { useSearchRuntime } from "../model/context.js";
|
|
57
|
+
import { readCardPhotos } from "./cardPhotos.js";
|
|
52
58
|
import { SEARCH_I18N_KEYS } from "../i18n/keys.js";
|
|
53
59
|
function text(value) {
|
|
54
60
|
if (typeof value === "string" && value.length > 0)
|
|
@@ -67,7 +73,11 @@ export const GENERIC_CARD_FIELDS = [
|
|
|
67
73
|
"price",
|
|
68
74
|
"currency",
|
|
69
75
|
"location",
|
|
70
|
-
|
|
76
|
+
// The gallery, and the singular first photo the projection keeps beside it
|
|
77
|
+
// (`images[0]`). Both hold CDN references; see `cardPhotos.ts`. The old
|
|
78
|
+
// `image_url` is gone — no backend in this fleet ever wrote it.
|
|
79
|
+
"images",
|
|
80
|
+
"image",
|
|
71
81
|
"url",
|
|
72
82
|
];
|
|
73
83
|
/**
|
|
@@ -137,41 +147,74 @@ const PROMOTED_TAG = {
|
|
|
137
147
|
lineHeight: `${String(fontSize.md.lineHeight)}px`,
|
|
138
148
|
whiteSpace: "nowrap",
|
|
139
149
|
};
|
|
140
|
-
/**
|
|
141
|
-
|
|
150
|
+
/**
|
|
151
|
+
* The shape of one photo well, everywhere on this card. Declared once so the
|
|
152
|
+
* placeholder and the loaded photo cannot drift into two different crops, and
|
|
153
|
+
* so the well is on screen before the network answers.
|
|
154
|
+
*/
|
|
155
|
+
const CARD_PHOTO_ASPECT = "4 / 3";
|
|
156
|
+
/** The placeholder well: the box the photo would have occupied. */
|
|
157
|
+
const PHOTO_ABSENT = {
|
|
158
|
+
display: "flex",
|
|
159
|
+
flexDirection: "column",
|
|
160
|
+
alignItems: "center",
|
|
161
|
+
justifyContent: "center",
|
|
162
|
+
gap: spacing[1],
|
|
142
163
|
width: "100%",
|
|
143
|
-
aspectRatio:
|
|
164
|
+
aspectRatio: CARD_PHOTO_ASPECT,
|
|
144
165
|
overflow: "hidden",
|
|
145
166
|
borderRadius: radii.md,
|
|
146
167
|
background: cssVar("surface-sunken"),
|
|
168
|
+
color: cssVar("text-muted"),
|
|
147
169
|
};
|
|
170
|
+
/** The slide's own fill — the strip's well owns the shape. */
|
|
171
|
+
const PHOTO_FILL = { width: "100%", height: "100%" };
|
|
172
|
+
/** A camera outline in `currentColor` — no icon dependency, and it inherits
|
|
173
|
+
* the theme rather than carrying a colour. `aria-hidden` because the sentence
|
|
174
|
+
* beside it is the label. */
|
|
175
|
+
function CameraGlyph() {
|
|
176
|
+
return (_jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [_jsx("path", { d: "M3 8.5A2.5 2.5 0 0 1 5.5 6h1.7l1.2-2h6.2l1.2 2h1.7A2.5 2.5 0 0 1 20 8.5v8A2.5 2.5 0 0 1 17.5 19h-11A2.5 2.5 0 0 1 4 16.5z" }), _jsx("circle", { cx: "12", cy: "12.5", r: "3.2" })] }));
|
|
177
|
+
}
|
|
148
178
|
/**
|
|
149
|
-
*
|
|
179
|
+
* The card's photos — the whole gallery, as a swipeable strip.
|
|
180
|
+
*
|
|
181
|
+
* ── Why the strip is OUTSIDE the card's anchor ────────────────────────────
|
|
150
182
|
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
183
|
+
* `<SkinCarousel>` is a scroll container with its own tab stop, and a
|
|
184
|
+
* horizontal swipe that ends inside an `<a>` is a swipe the browser may
|
|
185
|
+
* deliver as a click. Inside the anchor, every attempt to look at photo two
|
|
186
|
+
* would navigate to the result — the defect that makes phone galleries
|
|
187
|
+
* unusable. So it sits above the anchor as a sibling, exactly as
|
|
188
|
+
* `<ListingSerpCard>` does it, and the anchor still covers everything a
|
|
189
|
+
* person reads.
|
|
190
|
+
*
|
|
191
|
+
* Three states, and they are three different facts. No photo FIELD at all —
|
|
192
|
+
* a doc type that indexes text — draws nothing, because reserving a 4:3 well
|
|
193
|
+
* for a corpus that has no photos is a hole in every row. A field with
|
|
194
|
+
* nothing behind it draws the well and says the photo is unavailable: that is
|
|
195
|
+
* usually an unwired `resolveImage`, and a sentence gets it fixed where an
|
|
196
|
+
* empty grey box does not. Anything else is the strip.
|
|
155
197
|
*/
|
|
156
|
-
function
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
198
|
+
function CardPhotoStrip(props) {
|
|
199
|
+
const t = useT();
|
|
200
|
+
const { photos, title } = props;
|
|
201
|
+
if (photos.stored === 0)
|
|
202
|
+
return null;
|
|
203
|
+
if (photos.images.length === 0) {
|
|
204
|
+
const caption = t(SEARCH_I18N_KEYS.resultsPhotoUnavailable);
|
|
205
|
+
return (_jsxs("div", { role: "img", "aria-label": caption, "data-testid": "search-result-photo-absent", style: PHOTO_ABSENT, children: [_jsx(CameraGlyph, {}), _jsx(Typography.Text, { type: "secondary", style: { fontSize: fontSize.xs.fontSize }, children: caption })] }));
|
|
160
206
|
}
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
preview_b64: null,
|
|
173
|
-
variants: [],
|
|
174
|
-
};
|
|
207
|
+
const total = photos.images.length;
|
|
208
|
+
// A one-photo strip gets neither a peek nor dots: the sliver of a next
|
|
209
|
+
// slide is an affordance for something that is there.
|
|
210
|
+
const many = total > 1;
|
|
211
|
+
return (_jsx(SkinCarousel, { label: t(SEARCH_I18N_KEYS.resultsPhotos), aspectRatio: CARD_PHOTO_ASPECT, peek: many, dots: many, "data-testid": "search-result-photos", children: photos.images.map((image, index) => (_jsx(Image, { meta: image, alt: many
|
|
212
|
+
? t(SEARCH_I18N_KEYS.resultsPhotoAlt, {
|
|
213
|
+
index: index + 1,
|
|
214
|
+
total,
|
|
215
|
+
title,
|
|
216
|
+
})
|
|
217
|
+
: t(SEARCH_I18N_KEYS.resultsImageAlt, { title }), fit: "cover", "data-testid": "search-result-photo", style: PHOTO_FILL }, `${String(index)}:${image.url}`))) }));
|
|
175
218
|
}
|
|
176
219
|
/**
|
|
177
220
|
* The whole card as ONE tap target.
|
|
@@ -193,6 +236,7 @@ export function SearchResultCard(props) {
|
|
|
193
236
|
const t = useT();
|
|
194
237
|
const format = useFormat();
|
|
195
238
|
const card = props.item.card;
|
|
239
|
+
const resolve = useSearchRuntime().resolveImage;
|
|
196
240
|
const title = text(card["title"]) ?? t(SEARCH_I18N_KEYS.resultsUntitled);
|
|
197
241
|
const price = formatCardPrice(format, text(card["price"]), text(card["currency"]));
|
|
198
242
|
const href = text(card["url"]);
|
|
@@ -202,10 +246,12 @@ export function SearchResultCard(props) {
|
|
|
202
246
|
km: props.item.distance_km.toFixed(1),
|
|
203
247
|
})
|
|
204
248
|
: undefined;
|
|
205
|
-
// Memoised on the
|
|
206
|
-
//
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
249
|
+
// Memoised on the card and the resolver: a host resolver is a plain
|
|
250
|
+
// function returning a fresh object, so resolving inline hands `<Image>` a
|
|
251
|
+
// new `meta` identity on every render — a load it then has to decide is or
|
|
252
|
+
// is not the same one.
|
|
253
|
+
const photos = useMemo(() => readCardPhotos(card, resolve), [card, resolve]);
|
|
254
|
+
const body = (_jsxs(Flex, { vertical: true, gap: spacing[1], children: [_jsx(Typography.Text, { strong: true, children: title }), price !== undefined && (_jsx(Typography.Text, { strong: true, style: { fontSize: fontSize.lg.fontSize }, "data-testid": "search-result-price", children: price })), (location !== undefined || distance !== undefined) && (_jsx(Typography.Text, { type: "secondary", children: [location, distance].filter((v) => v !== undefined).join(" · ") }))] }));
|
|
255
|
+
return (_jsx(Card, { size: "small", "data-testid": "search-result-card", "data-promoted": props.item.promoted ? "true" : "false", ...(href !== undefined ? { hoverable: true } : {}), styles: { body: { padding: spacing[3] } }, children: _jsxs(Flex, { vertical: true, gap: spacing[2], children: [_jsx(CardPhotoStrip, { photos: photos, title: title }), href === undefined ? (body) : (_jsx("a", { href: href, style: CARD_LINK, "data-testid": "search-result-link", children: body })), props.item.promoted && (_jsxs(Flex, { vertical: true, gap: spacing[1], "data-testid": "search-result-promotion", children: [_jsx("span", { style: PROMOTED_TAG, "data-testid": "search-result-promoted", children: t(SEARCH_I18N_KEYS.resultsPromoted) }), _jsx(Typography.Text, { type: "secondary", style: { fontSize: fontSize.xs.fontSize }, "data-testid": "search-result-promoted-hint", children: t(SEARCH_I18N_KEYS.resultsPromotedHint) })] }))] }) }));
|
|
210
256
|
}
|
|
211
257
|
//# sourceMappingURL=SearchResultCard.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SearchResultCard.js","sourceRoot":"","sources":["../../src/default/SearchResultCard.tsx"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"SearchResultCard.js","sourceRoot":"","sources":["../../src/default/SearchResultCard.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAEhC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAE9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAUnD,SAAS,IAAI,CAAC,KAAc;IAC1B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAChE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9E,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAsB;IACpD,OAAO;IACP,OAAO;IACP,UAAU;IACV,UAAU;IACV,2EAA2E;IAC3E,wEAAwE;IACxE,gEAAgE;IAChE,QAAQ;IACR,OAAO;IACP,KAAK;CACN,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,eAAe,CAC7B,MAAc,EACd,KAAyB,EACzB,QAA4B;IAE5B,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC7B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAC;IAC3C,IAAI,QAAQ,KAAK,SAAS,IAAI,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7D,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE;gBAClC,KAAK,EAAE,UAAU;gBACjB,QAAQ,EAAE,QAAQ,CAAC,WAAW,EAAE;gBAChC,eAAe,EAAE,cAAc;gBAC/B,qBAAqB,EAAE,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;aACxD,CAAC,CAAC;YACH,IAAI,KAAK,KAAK,IAAI;gBAAE,OAAO,KAAK,CAAC;QACnC,CAAC;QAAC,MAAM,CAAC;YACP,oEAAoE;QACtE,CAAC;IACH,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACtC,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IACnC,OAAO,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,IAAI,QAAQ,EAAE,CAAC;AACrE,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,YAAY,GAAkB;IAClC,SAAS,EAAE,YAAY;IACvB,UAAU,EAAE,MAAM,CAAC,YAAY,CAAC;IAChC,2EAA2E;IAC3E,6EAA6E;IAC7E,KAAK,EAAE,MAAM,CAAC,SAAS,CAAC;IACxB,UAAU,EAAE,UAAU,CAAC,MAAM;IAC7B,MAAM,EAAE,aAAa,MAAM,CAAC,gBAAgB,CAAC,EAAE;IAC/C,YAAY,EAAE,KAAK,CAAC,EAAE;IACtB,OAAO,EAAE,KAAK,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI;IACpC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,QAAQ;IAC9B,UAAU,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI;IACjD,UAAU,EAAE,QAAQ;CACrB,CAAC;AAEF;;;;GAIG;AACH,MAAM,iBAAiB,GAAG,OAAO,CAAC;AAElC,mEAAmE;AACnE,MAAM,YAAY,GAAkB;IAClC,OAAO,EAAE,MAAM;IACf,aAAa,EAAE,QAAQ;IACvB,UAAU,EAAE,QAAQ;IACpB,cAAc,EAAE,QAAQ;IACxB,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC;IACf,KAAK,EAAE,MAAM;IACb,WAAW,EAAE,iBAAiB;IAC9B,QAAQ,EAAE,QAAQ;IAClB,YAAY,EAAE,KAAK,CAAC,EAAE;IACtB,UAAU,EAAE,MAAM,CAAC,gBAAgB,CAAC;IACpC,KAAK,EAAE,MAAM,CAAC,YAAY,CAAC;CAC5B,CAAC;AAEF,8DAA8D;AAC9D,MAAM,UAAU,GAAkB,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAEpE;;6BAE6B;AAC7B,SAAS,WAAW;IAClB,OAAO,CACL,eACE,KAAK,EAAC,IAAI,EACV,MAAM,EAAC,IAAI,EACX,OAAO,EAAC,WAAW,EACnB,IAAI,EAAC,MAAM,EACX,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,KAAK,EACjB,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,iBACV,MAAM,aAElB,eAAM,CAAC,EAAC,2HAA2H,GAAG,EACtI,iBAAQ,EAAE,EAAC,IAAI,EAAC,EAAE,EAAC,MAAM,EAAC,CAAC,EAAC,KAAK,GAAG,IAChC,CACP,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,SAAS,cAAc,CAAC,KAGvB;IACC,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;IAChC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAErC,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG,CAAC,CAAC,gBAAgB,CAAC,uBAAuB,CAAC,CAAC;QAC5D,OAAO,CACL,eACE,IAAI,EAAC,KAAK,gBACE,OAAO,iBACP,4BAA4B,EACxC,KAAK,EAAE,YAAY,aAEnB,KAAC,WAAW,KAAG,EACf,KAAC,UAAU,CAAC,IAAI,IAAC,IAAI,EAAC,WAAW,EAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,YACxE,OAAO,GACQ,IACd,CACP,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;IACnC,uEAAuE;IACvE,sDAAsD;IACtD,MAAM,IAAI,GAAG,KAAK,GAAG,CAAC,CAAC;IACvB,OAAO,CACL,KAAC,YAAY,IACX,KAAK,EAAE,CAAC,CAAC,gBAAgB,CAAC,aAAa,CAAC,EACxC,WAAW,EAAE,iBAAiB,EAC9B,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,IAAI,iBACE,sBAAsB,YAEjC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CACnC,KAAC,KAAK,IAEJ,IAAI,EAAE,KAAK,EACX,GAAG,EACD,IAAI;gBACF,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,eAAe,EAAE;oBAClC,KAAK,EAAE,KAAK,GAAG,CAAC;oBAChB,KAAK;oBACL,KAAK;iBACN,CAAC;gBACJ,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,eAAe,EAAE,EAAE,KAAK,EAAE,CAAC,EAEpD,GAAG,EAAC,OAAO,iBACC,qBAAqB,EACjC,KAAK,EAAE,UAAU,IAbZ,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,GAAG,EAAE,CAcpC,CACH,CAAC,GACW,CAChB,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,SAAS,GAAkB;IAC/B,KAAK,EAAE,SAAS;IAChB,OAAO,EAAE,OAAO;IAChB,cAAc,EAAE,MAAM;CACvB,CAAC;AAEF,MAAM,UAAU,gBAAgB,CAAC,KAAsB;IACrD,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;IACjB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;IAC7B,MAAM,OAAO,GAAG,gBAAgB,EAAE,CAAC,YAAY,CAAC;IAChD,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,gBAAgB,CAAC,eAAe,CAAC,CAAC;IACzE,MAAM,KAAK,GAAG,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IACnF,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IAC/B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;IACxC,MAAM,QAAQ,GACZ,KAAK,CAAC,IAAI,CAAC,WAAW,KAAK,IAAI;QAC7B,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,iBAAiB,EAAE;YACpC,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;SACtC,CAAC;QACJ,CAAC,CAAC,SAAS,CAAC;IAChB,oEAAoE;IACpE,2EAA2E;IAC3E,2EAA2E;IAC3E,uBAAuB;IACvB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;IAE7E,MAAM,IAAI,GAAG,CACX,MAAC,IAAI,IAAC,QAAQ,QAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,aAC5B,KAAC,UAAU,CAAC,IAAI,IAAC,MAAM,kBAAE,KAAK,GAAmB,EAIhD,KAAK,KAAK,SAAS,IAAI,CACtB,KAAC,UAAU,CAAC,IAAI,IACd,MAAM,QACN,KAAK,EAAE,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,iBAC7B,qBAAqB,YAEhC,KAAK,GACU,CACnB,EACA,CAAC,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,SAAS,CAAC,IAAI,CACrD,KAAC,UAAU,CAAC,IAAI,IAAC,IAAI,EAAC,WAAW,YAC9B,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAChD,CACnB,IACI,CACR,CAAC;IAEF,OAAO,CACL,KAAC,IAAI,IACH,IAAI,EAAC,OAAO,iBACA,oBAAoB,mBACjB,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,KACjD,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EACnD,MAAM,EAAE,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,YAEzC,MAAC,IAAI,IAAC,QAAQ,QAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,aAE5B,KAAC,cAAc,IAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAI,EAC/C,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,CACpB,IAAI,CACL,CAAC,CAAC,CAAC,CACF,YAAG,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,iBAAc,oBAAoB,YAC9D,IAAI,GACH,CACL,EACA,KAAK,CAAC,IAAI,CAAC,QAAQ,IAAI,CACtB,MAAC,IAAI,IAAC,QAAQ,QAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,iBAAc,yBAAyB,aACnE,eAAM,KAAK,EAAE,YAAY,iBAAc,wBAAwB,YAC5D,CAAC,CAAC,gBAAgB,CAAC,eAAe,CAAC,GAC/B,EAKP,KAAC,UAAU,CAAC,IAAI,IACd,IAAI,EAAC,WAAW,EAChB,KAAK,EAAE,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,iBAC7B,6BAA6B,YAExC,CAAC,CAAC,gBAAgB,CAAC,mBAAmB,CAAC,GACxB,IACb,CACR,IACI,GACF,CACR,CAAC;AACJ,CAAC"}
|