@stapel/listings-react 0.23.0 → 0.25.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 +93 -0
- package/MODULE.md +4 -0
- package/README.md +75 -0
- package/dist/api/generated/schema.d.ts +126 -11
- package/dist/api/generated/schema.d.ts.map +1 -1
- package/dist/api/listingsApi.d.ts +5 -3
- package/dist/api/listingsApi.d.ts.map +1 -1
- package/dist/api/listingsApi.js.map +1 -1
- package/dist/api/types.d.ts +51 -4
- package/dist/api/types.d.ts.map +1 -1
- package/dist/api/types.js.map +1 -1
- package/dist/default/CardBadges.d.ts +47 -0
- package/dist/default/CardBadges.d.ts.map +1 -0
- package/dist/default/CardBadges.js +38 -0
- package/dist/default/CardBadges.js.map +1 -0
- package/dist/default/ListingCard.d.ts.map +1 -1
- package/dist/default/ListingCard.js +6 -6
- package/dist/default/ListingCard.js.map +1 -1
- package/dist/default/ListingDetailPane.d.ts.map +1 -1
- package/dist/default/ListingDetailPane.js +12 -19
- package/dist/default/ListingDetailPane.js.map +1 -1
- package/dist/default/ListingPhoto.d.ts.map +1 -1
- package/dist/default/ListingPhoto.js +12 -8
- package/dist/default/ListingPhoto.js.map +1 -1
- package/dist/default/ListingSerpCard.d.ts.map +1 -1
- package/dist/default/ListingSerpCard.js +3 -5
- package/dist/default/ListingSerpCard.js.map +1 -1
- package/dist/default/ListingSpecList.d.ts +74 -0
- package/dist/default/ListingSpecList.d.ts.map +1 -0
- package/dist/default/ListingSpecList.js +90 -0
- package/dist/default/ListingSpecList.js.map +1 -0
- package/dist/default/cardGallery.d.ts +81 -0
- package/dist/default/cardGallery.d.ts.map +1 -0
- package/dist/default/cardGallery.js +266 -0
- package/dist/default/cardGallery.js.map +1 -0
- package/dist/default/index.d.ts +6 -0
- package/dist/default/index.d.ts.map +1 -1
- package/dist/default/index.js +5 -0
- package/dist/default/index.js.map +1 -1
- package/dist/headless/ListingComposer.d.ts +20 -2
- package/dist/headless/ListingComposer.d.ts.map +1 -1
- package/dist/headless/ListingComposer.js +19 -4
- package/dist/headless/ListingComposer.js.map +1 -1
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +1 -0
- package/dist/i18n/es.js.map +1 -1
- package/dist/i18n/generated/errors.es.gen.d.ts +1 -1
- package/dist/i18n/generated/errors.es.gen.js +1 -1
- package/dist/i18n/generated/errors.gen.d.ts +6 -0
- package/dist/i18n/generated/errors.gen.d.ts.map +1 -1
- package/dist/i18n/generated/errors.gen.js +3 -0
- package/dist/i18n/generated/errors.gen.js.map +1 -1
- package/dist/i18n/generated/errors.ru.gen.d.ts +1 -1
- package/dist/i18n/generated/errors.ru.gen.js +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 +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/model/cardBadges.d.ts +134 -0
- package/dist/model/cardBadges.d.ts.map +1 -0
- package/dist/model/cardBadges.js +131 -0
- package/dist/model/cardBadges.js.map +1 -0
- package/dist/model/draft.d.ts +10 -2
- package/dist/model/draft.d.ts.map +1 -1
- package/dist/model/draft.js +22 -4
- package/dist/model/draft.js.map +1 -1
- package/dist/model/featureText.d.ts +85 -0
- package/dist/model/featureText.d.ts.map +1 -0
- package/dist/model/featureText.js +153 -0
- package/dist/model/featureText.js.map +1 -0
- package/dist/model/features.d.ts.map +1 -1
- package/dist/model/features.js +59 -0
- package/dist/model/features.js.map +1 -1
- package/llms.txt +5 -2
- package/manifest.json +47 -1
- package/nav-manifest.json +1 -1
- package/package.json +10 -10
- package/src/analytics/generated/events.json +1 -1
- package/src/api/generated/schema.ts +126 -11
- package/src/api/listingsApi.ts +5 -3
- package/src/api/types.ts +60 -6
- package/src/default/CardBadges.tsx +105 -0
- package/src/default/ListingCard.tsx +7 -21
- package/src/default/ListingDetailPane.tsx +13 -37
- package/src/default/ListingPhoto.tsx +54 -26
- package/src/default/ListingSerpCard.tsx +10 -28
- package/src/default/ListingSpecList.tsx +195 -0
- package/src/default/cardGallery.ts +287 -0
- package/src/default/index.ts +22 -0
- package/src/headless/ListingComposer.tsx +39 -5
- package/src/i18n/es.ts +3 -0
- package/src/i18n/generated/errors.es.gen.ts +1 -1
- package/src/i18n/generated/errors.gen.ts +3 -0
- package/src/i18n/generated/errors.json +9 -0
- package/src/i18n/generated/errors.ru.gen.ts +1 -1
- package/src/i18n/ru.ts +5 -0
- package/src/index.ts +15 -0
- package/src/model/cardBadges.ts +237 -0
- package/src/model/draft.ts +23 -5
- package/src/model/featureText.ts +251 -0
- package/src/model/features.ts +60 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The CARD BADGE CONTRACT — what a badge on a result card is allowed to say.
|
|
3
|
+
*
|
|
4
|
+
* ── What was on screen ────────────────────────────────────────────────────
|
|
5
|
+
*
|
|
6
|
+
* A live card's badge line read "Brick · 3 · 9". Three true facts about a
|
|
7
|
+
* flat, and two of them unreadable: 3 what, 9 what. The projection carried
|
|
8
|
+
* the VALUES and the display half printed them, because that is all a stored
|
|
9
|
+
* `features_badges` row could offer — a number with no unit and no question
|
|
10
|
+
* beside it.
|
|
11
|
+
*
|
|
12
|
+
* stapel-listings 0.21.3 adds the missing half to each element:
|
|
13
|
+
*
|
|
14
|
+
* `value` the raw answer
|
|
15
|
+
* `label` its resolved copy, where the answer is an option
|
|
16
|
+
* `unit` what the number is measured in
|
|
17
|
+
* `name` the question — the feature's own display name
|
|
18
|
+
* `presentation` which of those to print, decided by the SERVER
|
|
19
|
+
*
|
|
20
|
+
* `presentation` is the load-bearing key, and it is the server's decision
|
|
21
|
+
* rather than a rule guessed here, because the right badge for a value
|
|
22
|
+
* depends on the CATEGORY: "3 rooms" wants its name, "Brick" is a
|
|
23
|
+
* boolean whose name IS the badge, "20 000 km" wants its unit and no name at
|
|
24
|
+
* all, and no client-side heuristic gets all three right for every catalogue
|
|
25
|
+
* in the fleet.
|
|
26
|
+
*
|
|
27
|
+
* ── The four presentations ───────────────────────────────────────────────
|
|
28
|
+
*
|
|
29
|
+
* `value` the value alone — "Brick" (an option label)
|
|
30
|
+
* `value_unit` value and unit — "20 000 km"
|
|
31
|
+
* `name_value` name and value — "Floor 3": a SPACE and never a colon,
|
|
32
|
+
* because a card is a caption and not a
|
|
33
|
+
* form. The unit is kept when the
|
|
34
|
+
* element carries one — a named row
|
|
35
|
+
* that dropped it reads "Mileage 20 000"
|
|
36
|
+
* `name` the name alone — a TRUE boolean, whose whole content
|
|
37
|
+
* is that the flat is brick. A FALSE
|
|
38
|
+
* one renders nothing: "not brick" is
|
|
39
|
+
* not a selling point, and a card is a
|
|
40
|
+
* summary rather than a form.
|
|
41
|
+
*
|
|
42
|
+
* ── Why a local type extension and not a regenerated schema ───────────────
|
|
43
|
+
*
|
|
44
|
+
* The generated `FeatureDao` union is already unusable on this side (the
|
|
45
|
+
* discriminator defect, `api/types.ts`), `features_badges` is a `JSONField`
|
|
46
|
+
* on the serializer, and this pair reads the row through
|
|
47
|
+
* {@link ListingFeatureDao} — a hand-written mirror of the runtime shape —
|
|
48
|
+
* for exactly that reason. The contract is an additive extension of the same
|
|
49
|
+
* mirror, so a regeneration cannot take it away and an older backend that
|
|
50
|
+
* sends none of the five keys is simply a row where
|
|
51
|
+
* {@link hasCardBadgeContract} is false.
|
|
52
|
+
*
|
|
53
|
+
* ── The fallback is the whole compatibility story ─────────────────────────
|
|
54
|
+
*
|
|
55
|
+
* A row with no `presentation` is a row from a server that predates the
|
|
56
|
+
* contract, and it is rendered exactly as it is rendered today — through
|
|
57
|
+
* `@stapel/attributes-react`'s `<FeatureBadges>`, off the stored DAO's own
|
|
58
|
+
* config. Nothing about this module is required for a card to draw.
|
|
59
|
+
*/
|
|
60
|
+
import type { ListingCardBadgeElement, ListingFeatureDao } from "../api/types.js";
|
|
61
|
+
/**
|
|
62
|
+
* Which parts of a badge element the server asked to be printed.
|
|
63
|
+
*
|
|
64
|
+
* Read off the GENERATED element rather than spelled again here: the four
|
|
65
|
+
* readings are the server's enum, and a fifth added upstream must turn this
|
|
66
|
+
* build red instead of falling through {@link badgePresentation}'s default in
|
|
67
|
+
* silence.
|
|
68
|
+
*/
|
|
69
|
+
export type CardBadgePresentation = ListingCardBadgeElement["presentation"];
|
|
70
|
+
/**
|
|
71
|
+
* The keys stapel-listings 0.21.3 adds to a `features_badges` /
|
|
72
|
+
* `features_title` element.
|
|
73
|
+
*
|
|
74
|
+
* The NAMES and their types come from the generated element; the optionality
|
|
75
|
+
* is this pair's own, and is the whole compatibility story — the contract
|
|
76
|
+
* makes them required, and a server that predates it sends none of them. Same
|
|
77
|
+
* argument as {@link ListingEngagementFields}, one file over: a generated type
|
|
78
|
+
* is a promise about the contract, not about the bytes a deployment sends.
|
|
79
|
+
*/
|
|
80
|
+
export type CardBadgeContract = {
|
|
81
|
+
/** The raw answer. Absent on a redacted row, which is why the renderers
|
|
82
|
+
* below all tolerate it, and `unknown` because the DAO union types it per
|
|
83
|
+
* feature type and a badge prints whatever it was given. */
|
|
84
|
+
readonly value?: unknown;
|
|
85
|
+
} & {
|
|
86
|
+
readonly [K in "label" | "unit" | "name"]?: ListingCardBadgeElement[K] | null;
|
|
87
|
+
} & {
|
|
88
|
+
/** Widened back to `string` on purpose: {@link badgePresentation} exists to
|
|
89
|
+
* REFUSE a reading it does not know, and a field typed as the four would
|
|
90
|
+
* make that refusal unreachable — and untestable — while a server is still
|
|
91
|
+
* free to send a fifth. */
|
|
92
|
+
readonly presentation?: string | null;
|
|
93
|
+
};
|
|
94
|
+
/** A stored badge row, with the contract's keys where the server sends them. */
|
|
95
|
+
export type CardBadgeRow = ListingFeatureDao & CardBadgeContract;
|
|
96
|
+
/**
|
|
97
|
+
* `presentation` as one of the four, or `undefined`.
|
|
98
|
+
*
|
|
99
|
+
* An unrecognised spelling is `undefined` rather than a guess: the whole
|
|
100
|
+
* point of the key is that the SERVER decided, and a client that invented a
|
|
101
|
+
* fifth reading would print one category's rule on another category's card.
|
|
102
|
+
*/
|
|
103
|
+
export declare function badgePresentation(row: CardBadgeRow): CardBadgePresentation | undefined;
|
|
104
|
+
/**
|
|
105
|
+
* Does this projection speak the contract at all?
|
|
106
|
+
*
|
|
107
|
+
* ONE row declaring a `presentation` is enough: the server writes the whole
|
|
108
|
+
* list in one pass, so a mixed list means a row the server deliberately left
|
|
109
|
+
* plain, and that row still renders (as its value alone) rather than
|
|
110
|
+
* dragging the other four back to the old path.
|
|
111
|
+
*/
|
|
112
|
+
export declare function hasCardBadgeContract(rows: readonly CardBadgeRow[]): boolean;
|
|
113
|
+
/**
|
|
114
|
+
* The element's answer, as a person reads it: the server's `label` when there
|
|
115
|
+
* is one, the value with its digits grouped when it is a number, the value
|
|
116
|
+
* verbatim otherwise.
|
|
117
|
+
*/
|
|
118
|
+
export declare function badgeValueText(row: CardBadgeRow, locale?: string): string;
|
|
119
|
+
/**
|
|
120
|
+
* One badge element → the text a card prints, or `undefined` for an element
|
|
121
|
+
* that has nothing to say (a false boolean, a blank value).
|
|
122
|
+
*
|
|
123
|
+
* The one place the four presentations are read. Every card surface calls
|
|
124
|
+
* this and none of them re-derives it, for the same reason `CardTarget`
|
|
125
|
+
* exists: three cards each re-deciding what a badge says is three places for
|
|
126
|
+
* "Brick · 3 · 9" to come back.
|
|
127
|
+
*/
|
|
128
|
+
export declare function cardBadgeText(row: CardBadgeRow, locale?: string): string | undefined;
|
|
129
|
+
/** Every element that has something to say, in the server's order. */
|
|
130
|
+
export declare function cardBadgeTexts(rows: readonly CardBadgeRow[], locale?: string): readonly {
|
|
131
|
+
readonly slug: string;
|
|
132
|
+
readonly text: string;
|
|
133
|
+
}[];
|
|
134
|
+
//# sourceMappingURL=cardBadges.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cardBadges.d.ts","sourceRoot":"","sources":["../../src/model/cardBadges.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH,OAAO,KAAK,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAElF;;;;;;;GAOG;AACH,MAAM,MAAM,qBAAqB,GAAG,uBAAuB,CAAC,cAAc,CAAC,CAAC;AAE5E;;;;;;;;;GASG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;gEAE4D;IAC5D,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B,GAAG;IACF,QAAQ,EAAE,CAAC,IAAI,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC,CAAC,EAAE,uBAAuB,CAAC,CAAC,CAAC,GAAG,IAAI;CAC9E,GAAG;IACF;;;+BAG2B;IAC3B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvC,CAAC;AAEF,gFAAgF;AAChF,MAAM,MAAM,YAAY,GAAG,iBAAiB,GAAG,iBAAiB,CAAC;AAEjE;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,YAAY,GAAG,qBAAqB,GAAG,SAAS,CAYtF;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,SAAS,YAAY,EAAE,GAAG,OAAO,CAE3E;AAqBD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAmBzE;AAOD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CA8BpF;AAED,sEAAsE;AACtE,wBAAgB,cAAc,CAC5B,IAAI,EAAE,SAAS,YAAY,EAAE,EAC7B,MAAM,CAAC,EAAE,MAAM,GACd,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAS7D"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `presentation` as one of the four, or `undefined`.
|
|
3
|
+
*
|
|
4
|
+
* An unrecognised spelling is `undefined` rather than a guess: the whole
|
|
5
|
+
* point of the key is that the SERVER decided, and a client that invented a
|
|
6
|
+
* fifth reading would print one category's rule on another category's card.
|
|
7
|
+
*/
|
|
8
|
+
export function badgePresentation(row) {
|
|
9
|
+
const raw = row.presentation;
|
|
10
|
+
if (typeof raw !== "string")
|
|
11
|
+
return undefined;
|
|
12
|
+
switch (raw) {
|
|
13
|
+
case "value":
|
|
14
|
+
case "value_unit":
|
|
15
|
+
case "name_value":
|
|
16
|
+
case "name":
|
|
17
|
+
return raw;
|
|
18
|
+
default:
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Does this projection speak the contract at all?
|
|
24
|
+
*
|
|
25
|
+
* ONE row declaring a `presentation` is enough: the server writes the whole
|
|
26
|
+
* list in one pass, so a mixed list means a row the server deliberately left
|
|
27
|
+
* plain, and that row still renders (as its value alone) rather than
|
|
28
|
+
* dragging the other four back to the old path.
|
|
29
|
+
*/
|
|
30
|
+
export function hasCardBadgeContract(rows) {
|
|
31
|
+
return rows.some((row) => badgePresentation(row) !== undefined);
|
|
32
|
+
}
|
|
33
|
+
/** Trimmed copy, or `""` — `null` is the shape the wire uses for absent. */
|
|
34
|
+
function text(value) {
|
|
35
|
+
return typeof value === "string" ? value.trim() : "";
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* How many decimals the wire actually stated.
|
|
39
|
+
*
|
|
40
|
+
* A card badge must not invent precision and must not lose it: the server
|
|
41
|
+
* sends "2.0" for an engine volume and `2` for a number of rooms, and JS
|
|
42
|
+
* cannot tell `2.0` from `2` once it is a number — so a STRING value keeps
|
|
43
|
+
* the decimals it was written with and a number keeps its own.
|
|
44
|
+
*/
|
|
45
|
+
function statedDecimals(raw) {
|
|
46
|
+
const written = typeof raw === "string" ? raw : String(raw);
|
|
47
|
+
const tail = written.split(".")[1];
|
|
48
|
+
return tail === undefined ? 0 : tail.replace(/[^0-9]/g, "").length;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The element's answer, as a person reads it: the server's `label` when there
|
|
52
|
+
* is one, the value with its digits grouped when it is a number, the value
|
|
53
|
+
* verbatim otherwise.
|
|
54
|
+
*/
|
|
55
|
+
export function badgeValueText(row, locale) {
|
|
56
|
+
const label = text(row.label);
|
|
57
|
+
if (label.length > 0)
|
|
58
|
+
return label;
|
|
59
|
+
const raw = row.value;
|
|
60
|
+
if (raw === null || raw === undefined || raw === "")
|
|
61
|
+
return "";
|
|
62
|
+
if (typeof raw === "boolean")
|
|
63
|
+
return "";
|
|
64
|
+
const parsed = typeof raw === "number" ? raw : Number(raw);
|
|
65
|
+
if (typeof raw !== "object" && Number.isFinite(parsed)) {
|
|
66
|
+
const digits = statedDecimals(raw);
|
|
67
|
+
try {
|
|
68
|
+
return new Intl.NumberFormat(locale, {
|
|
69
|
+
minimumFractionDigits: digits,
|
|
70
|
+
maximumFractionDigits: digits,
|
|
71
|
+
}).format(parsed);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return parsed.toFixed(digits);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return typeof raw === "object" ? "" : String(raw);
|
|
78
|
+
}
|
|
79
|
+
/** Is this element's value the `true` a `name`-presented boolean needs? */
|
|
80
|
+
function isTrue(raw) {
|
|
81
|
+
return raw === true || raw === 1 || raw === "true";
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* One badge element → the text a card prints, or `undefined` for an element
|
|
85
|
+
* that has nothing to say (a false boolean, a blank value).
|
|
86
|
+
*
|
|
87
|
+
* The one place the four presentations are read. Every card surface calls
|
|
88
|
+
* this and none of them re-derives it, for the same reason `CardTarget`
|
|
89
|
+
* exists: three cards each re-deciding what a badge says is three places for
|
|
90
|
+
* "Brick · 3 · 9" to come back.
|
|
91
|
+
*/
|
|
92
|
+
export function cardBadgeText(row, locale) {
|
|
93
|
+
const presentation = badgePresentation(row);
|
|
94
|
+
const name = text(row.name);
|
|
95
|
+
const unit = text(row.unit);
|
|
96
|
+
if (presentation === "name") {
|
|
97
|
+
// The name IS the badge, and only while the answer is yes.
|
|
98
|
+
return isTrue(row.value) && name.length > 0 ? name : undefined;
|
|
99
|
+
}
|
|
100
|
+
const value = badgeValueText(row, locale);
|
|
101
|
+
if (value.length === 0)
|
|
102
|
+
return undefined;
|
|
103
|
+
const withUnit = unit.length > 0 ? `${value} ${unit}` : value;
|
|
104
|
+
switch (presentation) {
|
|
105
|
+
case "value_unit":
|
|
106
|
+
return withUnit;
|
|
107
|
+
case "name_value":
|
|
108
|
+
// A SPACE, not a colon. "Floor 3" is a caption; "Floor: 3" is a form
|
|
109
|
+
// field, and a card is neither a form nor a table — the colon is the
|
|
110
|
+
// punctuation the spec list uses because a spec list IS the table.
|
|
111
|
+
// The unit rides along: a named row that dropped it reads "Mileage
|
|
112
|
+
// 20 000", which is the defect this contract exists to close.
|
|
113
|
+
return name.length > 0 ? `${name} ${withUnit}` : withUnit;
|
|
114
|
+
default:
|
|
115
|
+
// `value`, and an element the server left plain in a list that speaks
|
|
116
|
+
// the contract. The value alone, never the unit — the server said so.
|
|
117
|
+
return value;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/** Every element that has something to say, in the server's order. */
|
|
121
|
+
export function cardBadgeTexts(rows, locale) {
|
|
122
|
+
const out = [];
|
|
123
|
+
for (const row of rows) {
|
|
124
|
+
const printed = cardBadgeText(row, locale);
|
|
125
|
+
if (printed !== undefined && typeof row.slug === "string" && row.slug.length > 0) {
|
|
126
|
+
out.push({ slug: row.slug, text: printed });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=cardBadges.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cardBadges.js","sourceRoot":"","sources":["../../src/model/cardBadges.ts"],"names":[],"mappings":"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"}
|
package/dist/model/draft.d.ts
CHANGED
|
@@ -111,15 +111,23 @@ export declare function draftValuesFromDetail(detail: ListingDetail, featuresDto
|
|
|
111
111
|
*/
|
|
112
112
|
export declare function draftPatchFromValues(values: ListingDraftValues, features: readonly FeatureDef[]): ListingDraftPatch;
|
|
113
113
|
/**
|
|
114
|
-
* The body for CREATING a draft: `
|
|
114
|
+
* The body for CREATING a draft: the category if there is one, `{}` if there
|
|
115
|
+
* is not.
|
|
115
116
|
*
|
|
116
117
|
* `perform_create` forces `owner` and `status`, and everything else has a
|
|
117
118
|
* model default, so a create that also carried the form's current contents
|
|
118
119
|
* would be a second write of data the very next `save-draft` sends anyway —
|
|
119
120
|
* and would fail the whole submission on a field the person could still fix.
|
|
120
121
|
* Create the row, then save into it.
|
|
122
|
+
*
|
|
123
|
+
* `{}` IS a valid create body since stapel-listings 0.21.4 made `category_id`
|
|
124
|
+
* nullable on a draft: a draft may exist before its category is chosen, and
|
|
125
|
+
* `publish` is where the category becomes mandatory (`publish_validation
|
|
126
|
+
* _failed` naming `category_id`). Not being able to create the row first is
|
|
127
|
+
* what left an analysis job addressed by the draft id with no id to start
|
|
128
|
+
* from (D261).
|
|
121
129
|
*/
|
|
122
|
-
export declare function createDraftBody(categoryId
|
|
130
|
+
export declare function createDraftBody(categoryId?: string): ListingDraftPatch;
|
|
123
131
|
/**
|
|
124
132
|
* Switching category: keep the answers whose slug the new schema also
|
|
125
133
|
* declares, drop the rest.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"draft.d.ts","sourceRoot":"","sources":["../../src/model/draft.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAExE,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,iBAAiB,EAClB,MAAM,iBAAiB,CAAC;AAGzB;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,eAAe;IAC9B,mEAAmE;IACnE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,yDAAyD;IACzD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B;mEAC+D;IAC/D,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,cAAc,EAAE,eAM5B,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,kDAAkD;IAClD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;iFAE6E;IAC7E,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACrD,0DAA0D;IAC1D,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;CACjC;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;iDACiD;AACjD,wBAAgB,gBAAgB,CAC9B,OAAO,GAAE,iBAAsB,GAC9B,kBAAkB,CAepB;AAMD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,YAAY,EACnB,OAAO,GAAE,iBAAsB,GAC9B,kBAAkB,CAwBpB;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,aAAa,EACrB,WAAW,EAAE,WAAW,EACxB,OAAO,GAAE,iBAAsB,GAC9B,kBAAkB,
|
|
1
|
+
{"version":3,"file":"draft.d.ts","sourceRoot":"","sources":["../../src/model/draft.ts"],"names":[],"mappings":"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"}
|
package/dist/model/draft.js
CHANGED
|
@@ -72,7 +72,11 @@ export function draftValuesFromWire(draft, options = {}) {
|
|
|
72
72
|
export function draftValuesFromDetail(detail, featuresDto, options = {}) {
|
|
73
73
|
const base = emptyDraftValues(options);
|
|
74
74
|
return {
|
|
75
|
-
|
|
75
|
+
// `text()` for the same reason as the draft seed above: 0.21.4 answers
|
|
76
|
+
// `category_id: null` for a row created before its category was chosen,
|
|
77
|
+
// and a seed that took it verbatim put `null` where a string is declared
|
|
78
|
+
// and crashed the first control that measured its length.
|
|
79
|
+
categoryId: text(detail.category_id),
|
|
76
80
|
title: text(detail.title),
|
|
77
81
|
description: text(detail.description),
|
|
78
82
|
price: text(detail.price),
|
|
@@ -125,7 +129,11 @@ function toWireFeatures(features, values) {
|
|
|
125
129
|
*/
|
|
126
130
|
export function draftPatchFromValues(values, features) {
|
|
127
131
|
return {
|
|
128
|
-
|
|
132
|
+
// Omitted while unchosen rather than sent as `""`: a draft is allowed to
|
|
133
|
+
// have no category (0.21.4), and `""` is not "no category" on the wire —
|
|
134
|
+
// it is an empty id the serializer refuses. The category is written by
|
|
135
|
+
// whichever save follows the pick.
|
|
136
|
+
...(values.categoryId.length > 0 ? { category_id: values.categoryId } : {}),
|
|
129
137
|
title_draft: values.title,
|
|
130
138
|
description_draft: values.description,
|
|
131
139
|
price_draft: values.price.length > 0 ? values.price : null,
|
|
@@ -149,16 +157,26 @@ export function draftPatchFromValues(values, features) {
|
|
|
149
157
|
};
|
|
150
158
|
}
|
|
151
159
|
/**
|
|
152
|
-
* The body for CREATING a draft: `
|
|
160
|
+
* The body for CREATING a draft: the category if there is one, `{}` if there
|
|
161
|
+
* is not.
|
|
153
162
|
*
|
|
154
163
|
* `perform_create` forces `owner` and `status`, and everything else has a
|
|
155
164
|
* model default, so a create that also carried the form's current contents
|
|
156
165
|
* would be a second write of data the very next `save-draft` sends anyway —
|
|
157
166
|
* and would fail the whole submission on a field the person could still fix.
|
|
158
167
|
* Create the row, then save into it.
|
|
168
|
+
*
|
|
169
|
+
* `{}` IS a valid create body since stapel-listings 0.21.4 made `category_id`
|
|
170
|
+
* nullable on a draft: a draft may exist before its category is chosen, and
|
|
171
|
+
* `publish` is where the category becomes mandatory (`publish_validation
|
|
172
|
+
* _failed` naming `category_id`). Not being able to create the row first is
|
|
173
|
+
* what left an analysis job addressed by the draft id with no id to start
|
|
174
|
+
* from (D261).
|
|
159
175
|
*/
|
|
160
176
|
export function createDraftBody(categoryId) {
|
|
161
|
-
return
|
|
177
|
+
return categoryId !== undefined && categoryId.length > 0
|
|
178
|
+
? { category_id: categoryId }
|
|
179
|
+
: {};
|
|
162
180
|
}
|
|
163
181
|
/**
|
|
164
182
|
* Switching category: keep the answers whose slug the new schema also
|
package/dist/model/draft.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"draft.js","sourceRoot":"","sources":["../../src/model/draft.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAM1E,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAgC3D,MAAM,CAAC,MAAM,cAAc,GAAoB;IAC7C,UAAU,EAAE,EAAE;IACd,aAAa,EAAE,EAAE;IACjB,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,OAAO,EAAE,EAAE;CACZ,CAAC;AAqCF;iDACiD;AACjD,MAAM,UAAU,gBAAgB,CAC9B,UAA6B,EAAE;IAE/B,OAAO;QACL,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,EAAE;QACpC,KAAK,EAAE,EAAE;QACT,WAAW,EAAE,EAAE;QACf,KAAK,EAAE,EAAE;QACT,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,wBAAwB;QACtD,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,EAAE;QAChC,MAAM,EAAE,EAAE;QACV,QAAQ,EAAE,cAAc;QACxB,QAAQ,EAAE,EAAE;QACZ,SAAS,EAAE,KAAK;QAChB,aAAa,EAAE,IAAI;QACnB,aAAa,EAAE,KAAK;KACrB,CAAC;AACJ,CAAC;AAED,SAAS,IAAI,CAAC,KAAgC;IAC5C,OAAO,KAAK,IAAI,EAAE,CAAC;AACrB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CACjC,KAAmB,EACnB,UAA6B,EAAE;IAE/B,MAAM,IAAI,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACvC,OAAO;QACL,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC;QACnC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC;QAC9B,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC;QAC1C,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC;QAC9B,QAAQ,EAAE,KAAK,CAAC,QAAQ,KAAK,SAAS,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC;YACjE,CAAC,CAAC,KAAK,CAAC,QAAQ;YAChB,CAAC,CAAC,IAAI,CAAC,QAAQ;QACjB,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;QAC9B,MAAM,EAAE,KAAK,CAAC,YAAY,IAAI,EAAE;QAChC,QAAQ,EAAE;YACR,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC;YACzC,aAAa,EAAE,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC;YAC/C,GAAG,EAAE,KAAK,CAAC,SAAS,IAAI,IAAI;YAC5B,GAAG,EAAE,KAAK,CAAC,SAAS,IAAI,IAAI;YAC5B,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC;SACnC;QACD,QAAQ,EAAE,eAAe,CAAC,CAAC,KAAK,CAAC,cAAc,IAAI,EAAE,CAAgB,CAAC;QACtE,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,KAAK;QACnC,aAAa,EAAE,KAAK,CAAC,cAAc,IAAI,IAAI;QAC3C,aAAa,EAAE,KAAK,CAAC,cAAc,IAAI,KAAK;KAC7C,CAAC;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,qBAAqB,CACnC,MAAqB,EACrB,WAAwB,EACxB,UAA6B,EAAE;IAE/B,MAAM,IAAI,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACvC,OAAO;QACL,UAAU,EAAE,MAAM,CAAC,WAAW;
|
|
1
|
+
{"version":3,"file":"draft.js","sourceRoot":"","sources":["../../src/model/draft.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A stored feature value as a person READS it — with its unit, and with its
|
|
3
|
+
* digits grouped the way the reader's locale groups them.
|
|
4
|
+
*
|
|
5
|
+
* ── What was on screen ────────────────────────────────────────────────────
|
|
6
|
+
*
|
|
7
|
+
* Measured on the live listing page: "Power 173", "Mileage 20000", "Engine
|
|
8
|
+
* volume 2.0". Three defects in three rows:
|
|
9
|
+
*
|
|
10
|
+
* - **no unit.** 173 what. 20000 what. A spec row whose value is a bare
|
|
11
|
+
* number is a row a buyer has to guess at, and mileage is the single
|
|
12
|
+
* number a used-car buyer decides on;
|
|
13
|
+
* - **no digit grouping.** `20000` is read digit by digit; "20 000" is read
|
|
14
|
+
* at a glance. `formatFeatureValue`'s integer branch is `String(value)`
|
|
15
|
+
* (attributes-react `src/format.ts:196`), deliberately — it mirrors the
|
|
16
|
+
* engine's own `str(value)` — and the engine renders for a machine-read
|
|
17
|
+
* API, not for a spec table;
|
|
18
|
+
* - **the wrong decimal mark.** `2.0` in a Russian storefront. The engine
|
|
19
|
+
* writes `toFixed`, which is invariant by construction.
|
|
20
|
+
*
|
|
21
|
+
* ── Where the unit comes from ─────────────────────────────────────────────
|
|
22
|
+
*
|
|
23
|
+
* There is NO generic `unit` key anywhere in this fleet's feature contract —
|
|
24
|
+
* not on `FeatureDef` (`stapel-attributes/base.py:154-208`), not on
|
|
25
|
+
* `IntConfig`/`FloatConfig` (`attributes-react/src/generated/featureDef.ts:163`),
|
|
26
|
+
* not on the stored DAO. The unit of an `int`/`float` IS its `postfix`, free
|
|
27
|
+
* text on the type's config, with `postfix1000` as the abbreviated unit the
|
|
28
|
+
* engine switches to at a thousand. `convertible_unit` is the one type with
|
|
29
|
+
* real unit semantics (`unitType`/`unit_m`/`unit_i`), and its own formatter
|
|
30
|
+
* already appends the resolved code — so this module leaves it alone.
|
|
31
|
+
*
|
|
32
|
+
* So "render the unit from the definition" means: read `postfix`. It reaches
|
|
33
|
+
* a display surface two ways, and `model/features.ts` now tries both — the
|
|
34
|
+
* stored row's own config first (`dto_to_dao` copies `postfix` at write
|
|
35
|
+
* time), then the CATEGORY's definition of the same slug, which is the path
|
|
36
|
+
* that repairs every listing published before a catalogue gained its units.
|
|
37
|
+
*
|
|
38
|
+
* ── Why the whole formatter is not delegated ──────────────────────────────
|
|
39
|
+
*
|
|
40
|
+
* `formatFeatureValue` is the fleet's one formatter and stays it: every type
|
|
41
|
+
* but `int` and `float` goes straight through it, untouched. The two numeric
|
|
42
|
+
* types are intercepted because the change is not a different ANSWER, it is
|
|
43
|
+
* the same answer typeset — same value, same precision rule, same
|
|
44
|
+
* `postfix1000` switch at a thousand, same translated unit, with the digits
|
|
45
|
+
* run through `Intl.NumberFormat` instead of `String()`.
|
|
46
|
+
*
|
|
47
|
+
* ── Which numbers are grouped, and which are not (D307) ────────────────────
|
|
48
|
+
*
|
|
49
|
+
* Grouping every number is how a card came to read "2 024" for a model year.
|
|
50
|
+
* A year is not a quantity: it is an identifier spelled in digits, and so is
|
|
51
|
+
* a house number, a floor, a room count. Grouping them is not a nicety
|
|
52
|
+
* applied too widely, it is the wrong reading.
|
|
53
|
+
*
|
|
54
|
+
* The rule is the one fact the catalogue already states, plus a magnitude no
|
|
55
|
+
* identifier reaches:
|
|
56
|
+
*
|
|
57
|
+
* - a feature that carries a UNIT (`prefix`, `postfix`, `postfix1000`) is a
|
|
58
|
+
* measurement — "20 000 km", "173 hp", "42 m²" — and is grouped;
|
|
59
|
+
* - a unitless value is grouped only from 10 000 up. A year is four digits
|
|
60
|
+
* for the next eight thousand of them and a count of doors is one, so the
|
|
61
|
+
* threshold sorts years from counts WITHOUT a "does this slug look like a
|
|
62
|
+
* year" heuristic — which is guesswork the moment a deployment names the
|
|
63
|
+
* slug in its own language.
|
|
64
|
+
*
|
|
65
|
+
* Grouping is switched off through `Intl` rather than around it, so a unitless
|
|
66
|
+
* float still gets the reader's DECIMAL MARK ("2,5", not "2.5"): the two are
|
|
67
|
+
* separate defects and only one of them is being repaired here.
|
|
68
|
+
*/
|
|
69
|
+
import type { FeatureDef, FeatureValueDto, FormatOptions } from "@stapel/attributes-react";
|
|
70
|
+
/**
|
|
71
|
+
* The unit a numeric feature is measured in, as the reader sees it — or `""`.
|
|
72
|
+
*
|
|
73
|
+
* `postfix1000` when the engine's own thousand switch applies, `postfix`
|
|
74
|
+
* otherwise, so the unit and the number can never disagree about scale.
|
|
75
|
+
*/
|
|
76
|
+
export declare function featureUnit(feature: FeatureDef, dto: FeatureValueDto | undefined, options?: FormatOptions): string;
|
|
77
|
+
/**
|
|
78
|
+
* A stored value as a spec row prints it: `formatFeatureValue` for every type
|
|
79
|
+
* but the two numeric ones, which are typeset here instead.
|
|
80
|
+
*
|
|
81
|
+
* Signature-compatible with `formatFeatureValue` on purpose — a surface
|
|
82
|
+
* swaps one for the other and nothing else changes.
|
|
83
|
+
*/
|
|
84
|
+
export declare function formatSpecValue(feature: FeatureDef, dto: FeatureValueDto | undefined, options?: FormatOptions): string | undefined;
|
|
85
|
+
//# sourceMappingURL=featureText.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"featureText.d.ts","sourceRoot":"","sources":["../../src/model/featureText.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmEG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AA+B3F;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,UAAU,EACnB,GAAG,EAAE,eAAe,GAAG,SAAS,EAChC,OAAO,CAAC,EAAE,aAAa,GACtB,MAAM,CAOR;AAoHD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,UAAU,EACnB,GAAG,EAAE,eAAe,GAAG,SAAS,EAChC,OAAO,CAAC,EAAE,aAAa,GACtB,MAAM,GAAG,SAAS,CAOpB"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { featureConfig, featureType, formatFeatureValue } from "@stapel/attributes-react";
|
|
2
|
+
/** The engine's own defaults (`types/int/type.py`, `types/float/type.py`). */
|
|
3
|
+
const DEFAULT_DIGITS = {
|
|
4
|
+
int: 1,
|
|
5
|
+
float: 2,
|
|
6
|
+
};
|
|
7
|
+
function str(value) {
|
|
8
|
+
return typeof value === "string" ? value : "";
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A config string the engine declares to be a translation key, resolved.
|
|
12
|
+
*
|
|
13
|
+
* `prefix`/`postfix`/`postfix1000` are collected by upstream's
|
|
14
|
+
* `get_translation_keys` unconditionally, so they go through the host's
|
|
15
|
+
* catalogue exactly as `formatFeatureValue` sends them — a Russian storefront
|
|
16
|
+
* was reading "L" and "each" off its own spec table before that landed, and
|
|
17
|
+
* this path must not reintroduce it.
|
|
18
|
+
*/
|
|
19
|
+
function translated(options, raw) {
|
|
20
|
+
const text = str(raw);
|
|
21
|
+
if (text.length === 0)
|
|
22
|
+
return "";
|
|
23
|
+
const t = options?.t;
|
|
24
|
+
if (t === undefined)
|
|
25
|
+
return text;
|
|
26
|
+
const resolved = t(text);
|
|
27
|
+
return resolved.length > 0 ? resolved : text;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The unit a numeric feature is measured in, as the reader sees it — or `""`.
|
|
31
|
+
*
|
|
32
|
+
* `postfix1000` when the engine's own thousand switch applies, `postfix`
|
|
33
|
+
* otherwise, so the unit and the number can never disagree about scale.
|
|
34
|
+
*/
|
|
35
|
+
export function featureUnit(feature, dto, options) {
|
|
36
|
+
const type = featureType(feature);
|
|
37
|
+
if (type !== "int" && type !== "float")
|
|
38
|
+
return "";
|
|
39
|
+
const config = featureConfig(feature);
|
|
40
|
+
const parsed = Number(dto?.value);
|
|
41
|
+
const big = str(config["postfix1000"]).length > 0 && Number.isFinite(parsed) && Math.abs(parsed) >= 1000;
|
|
42
|
+
return translated(options, config[big ? "postfix1000" : "postfix"]);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* `Intl.NumberFormat` for one value, or `undefined` when the runtime has no
|
|
46
|
+
* `Intl` for this locale to offer.
|
|
47
|
+
*
|
|
48
|
+
* A bad BCP-47 tag throws `RangeError`, and a spec table that renders nothing
|
|
49
|
+
* because a host passed `"en_US"` would be a worse outcome than an ungrouped
|
|
50
|
+
* number — so the caller falls back rather than the page failing.
|
|
51
|
+
*/
|
|
52
|
+
function localized(value, digits, locale, useGrouping) {
|
|
53
|
+
try {
|
|
54
|
+
return new Intl.NumberFormat(locale, {
|
|
55
|
+
minimumFractionDigits: digits,
|
|
56
|
+
maximumFractionDigits: digits,
|
|
57
|
+
useGrouping,
|
|
58
|
+
}).format(value);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Digits below which a unitless number is an identifier rather than a
|
|
65
|
+
* quantity — see the module header. */
|
|
66
|
+
const GROUPING_FLOOR = 10_000;
|
|
67
|
+
/**
|
|
68
|
+
* Does this feature measure something?
|
|
69
|
+
*
|
|
70
|
+
* The RAW config, not the translated unit: whether a value is a measurement
|
|
71
|
+
* is a fact about the catalogue, and it must not change because a host's
|
|
72
|
+
* message catalogue happens to resolve a unit key to an empty string.
|
|
73
|
+
*/
|
|
74
|
+
function hasUnit(config) {
|
|
75
|
+
return (str(config["prefix"]).length > 0 ||
|
|
76
|
+
str(config["postfix"]).length > 0 ||
|
|
77
|
+
str(config["postfix1000"]).length > 0);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The one numeric row, typeset — see the module header for what each piece
|
|
81
|
+
* is repairing.
|
|
82
|
+
*
|
|
83
|
+
* Returns `undefined` for a value that is not a number, so the caller falls
|
|
84
|
+
* back to the shared formatter and an unreadable row keeps saying so.
|
|
85
|
+
*/
|
|
86
|
+
function formatNumeric(feature, dto, kind, options) {
|
|
87
|
+
// `Number(null)` is 0 and `Number("")` is 0, so a blank row would render as
|
|
88
|
+
// a confident zero. The shared formatter's own `isBlank` gate is upstream of
|
|
89
|
+
// this branch and must be restated here rather than assumed.
|
|
90
|
+
const raw = dto.value;
|
|
91
|
+
if (raw === null || raw === undefined || raw === "")
|
|
92
|
+
return undefined;
|
|
93
|
+
const parsed = typeof raw === "number" ? raw : Number(raw);
|
|
94
|
+
if (!Number.isFinite(parsed))
|
|
95
|
+
return undefined;
|
|
96
|
+
const config = featureConfig(feature);
|
|
97
|
+
const precision = config["precision"];
|
|
98
|
+
const digits = typeof precision === "number" && precision >= 0 ? precision : DEFAULT_DIGITS[kind];
|
|
99
|
+
const prefix = translated(options, config["prefix"]);
|
|
100
|
+
const postfix1000 = str(config["postfix1000"]);
|
|
101
|
+
// The engine switches unit AND scale at exactly a thousand
|
|
102
|
+
// (`format_value`: `value / 1000`, trailing zeros stripped, `postfix1000`
|
|
103
|
+
// as the unit). Grouping a scaled number is harmless and keeps one code
|
|
104
|
+
// path; the trailing-zero strip is the engine's, done on the plain digits
|
|
105
|
+
// before the locale ever sees them, because "1.50" and "1,5" are the same
|
|
106
|
+
// decision made in two places.
|
|
107
|
+
if (postfix1000.length > 0 && Math.abs(parsed) >= 1000) {
|
|
108
|
+
const scaled = Number((parsed / 1000).toFixed(digits));
|
|
109
|
+
const body = localized(scaled, decimalsOf(scaled, digits), options?.locale, true) ??
|
|
110
|
+
String(scaled);
|
|
111
|
+
return join(prefix, body, translated(options, postfix1000));
|
|
112
|
+
}
|
|
113
|
+
// `precision` drives ONLY the scaled branch of an int — the engine's plain
|
|
114
|
+
// branch is `str(value)`, and an integer never grows a decimal tail
|
|
115
|
+
// whatever the config says (D26: a live category shipped `precision: 1` on
|
|
116
|
+
// a year field and the card read "2024.0").
|
|
117
|
+
const value = kind === "int" ? Math.trunc(parsed) : parsed;
|
|
118
|
+
const fraction = kind === "int" ? 0 : digits;
|
|
119
|
+
// A measurement is grouped; a unitless number is an identifier until it is
|
|
120
|
+
// too big to be one (D307 — the card read "2 024" for a model year).
|
|
121
|
+
const group = hasUnit(config) || Math.abs(value) >= GROUPING_FLOOR;
|
|
122
|
+
const body = localized(value, fraction, options?.locale, group) ?? value.toFixed(fraction);
|
|
123
|
+
return join(prefix, body, translated(options, config["postfix"]));
|
|
124
|
+
}
|
|
125
|
+
/** How many decimals a scaled value actually has, capped at the configured
|
|
126
|
+
* precision — the engine's `.rstrip('0').rstrip('.')`, expressed as a count
|
|
127
|
+
* so `Intl` can do the rendering. */
|
|
128
|
+
function decimalsOf(value, cap) {
|
|
129
|
+
const tail = value.toFixed(cap).split(".")[1] ?? "";
|
|
130
|
+
return tail.replace(/0+$/, "").length;
|
|
131
|
+
}
|
|
132
|
+
/** `prefix` + body + unit, with the single space the engine puts between a
|
|
133
|
+
* number and its unit and no space at all where there is no unit. */
|
|
134
|
+
function join(prefix, body, unit) {
|
|
135
|
+
return unit.length > 0 ? `${prefix}${body} ${unit}` : `${prefix}${body}`;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* A stored value as a spec row prints it: `formatFeatureValue` for every type
|
|
139
|
+
* but the two numeric ones, which are typeset here instead.
|
|
140
|
+
*
|
|
141
|
+
* Signature-compatible with `formatFeatureValue` on purpose — a surface
|
|
142
|
+
* swaps one for the other and nothing else changes.
|
|
143
|
+
*/
|
|
144
|
+
export function formatSpecValue(feature, dto, options) {
|
|
145
|
+
const type = featureType(feature);
|
|
146
|
+
if (dto !== undefined && (type === "int" || type === "float")) {
|
|
147
|
+
const typeset = formatNumeric(feature, dto, type, options);
|
|
148
|
+
if (typeset !== undefined)
|
|
149
|
+
return typeset;
|
|
150
|
+
}
|
|
151
|
+
return formatFeatureValue(feature, dto, options);
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=featureText.js.map
|