@stapel/listings-react 0.24.0 → 0.25.1
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 +18 -0
- package/MODULE.md +4 -0
- package/README.md +19 -0
- package/dist/api/generated/schema.d.ts +7 -5
- 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 +5 -1
- package/dist/api/types.d.ts.map +1 -1
- package/dist/api/types.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.js +1 -1
- package/dist/i18n/es.js.map +1 -1
- package/dist/i18n/keys.js +1 -1
- package/dist/i18n/keys.js.map +1 -1
- package/dist/i18n/ru.js +1 -1
- package/dist/i18n/ru.js.map +1 -1
- 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 +22 -0
- package/dist/model/featureText.d.ts.map +1 -1
- package/dist/model/featureText.js +23 -3
- package/dist/model/featureText.js.map +1 -1
- package/llms.txt +1 -1
- package/manifest.json +1 -1
- package/nav-manifest.json +1 -1
- package/package.json +6 -6
- package/src/analytics/generated/events.json +1 -1
- package/src/api/generated/schema.ts +7 -5
- package/src/api/listingsApi.ts +5 -3
- package/src/api/types.ts +5 -1
- package/src/headless/ListingComposer.tsx +39 -5
- package/src/i18n/es.ts +1 -1
- package/src/i18n/keys.ts +1 -1
- package/src/i18n/ru.ts +1 -1
- package/src/model/draft.ts +23 -5
- package/src/model/featureText.ts +55 -3
package/src/model/draft.ts
CHANGED
|
@@ -171,7 +171,11 @@ export function draftValuesFromDetail(
|
|
|
171
171
|
): ListingDraftValues {
|
|
172
172
|
const base = emptyDraftValues(options);
|
|
173
173
|
return {
|
|
174
|
-
|
|
174
|
+
// `text()` for the same reason as the draft seed above: 0.21.4 answers
|
|
175
|
+
// `category_id: null` for a row created before its category was chosen,
|
|
176
|
+
// and a seed that took it verbatim put `null` where a string is declared
|
|
177
|
+
// and crashed the first control that measured its length.
|
|
178
|
+
categoryId: text(detail.category_id),
|
|
175
179
|
title: text(detail.title),
|
|
176
180
|
description: text(detail.description),
|
|
177
181
|
price: text(detail.price),
|
|
@@ -236,7 +240,11 @@ export function draftPatchFromValues(
|
|
|
236
240
|
features: readonly FeatureDef[]
|
|
237
241
|
): ListingDraftPatch {
|
|
238
242
|
return {
|
|
239
|
-
|
|
243
|
+
// Omitted while unchosen rather than sent as `""`: a draft is allowed to
|
|
244
|
+
// have no category (0.21.4), and `""` is not "no category" on the wire —
|
|
245
|
+
// it is an empty id the serializer refuses. The category is written by
|
|
246
|
+
// whichever save follows the pick.
|
|
247
|
+
...(values.categoryId.length > 0 ? { category_id: values.categoryId } : {}),
|
|
240
248
|
title_draft: values.title,
|
|
241
249
|
description_draft: values.description,
|
|
242
250
|
price_draft: values.price.length > 0 ? values.price : null,
|
|
@@ -261,16 +269,26 @@ export function draftPatchFromValues(
|
|
|
261
269
|
}
|
|
262
270
|
|
|
263
271
|
/**
|
|
264
|
-
* The body for CREATING a draft: `
|
|
272
|
+
* The body for CREATING a draft: the category if there is one, `{}` if there
|
|
273
|
+
* is not.
|
|
265
274
|
*
|
|
266
275
|
* `perform_create` forces `owner` and `status`, and everything else has a
|
|
267
276
|
* model default, so a create that also carried the form's current contents
|
|
268
277
|
* would be a second write of data the very next `save-draft` sends anyway —
|
|
269
278
|
* and would fail the whole submission on a field the person could still fix.
|
|
270
279
|
* Create the row, then save into it.
|
|
280
|
+
*
|
|
281
|
+
* `{}` IS a valid create body since stapel-listings 0.21.4 made `category_id`
|
|
282
|
+
* nullable on a draft: a draft may exist before its category is chosen, and
|
|
283
|
+
* `publish` is where the category becomes mandatory (`publish_validation
|
|
284
|
+
* _failed` naming `category_id`). Not being able to create the row first is
|
|
285
|
+
* what left an analysis job addressed by the draft id with no id to start
|
|
286
|
+
* from (D261).
|
|
271
287
|
*/
|
|
272
|
-
export function createDraftBody(categoryId
|
|
273
|
-
return
|
|
288
|
+
export function createDraftBody(categoryId?: string): ListingDraftPatch {
|
|
289
|
+
return categoryId !== undefined && categoryId.length > 0
|
|
290
|
+
? { category_id: categoryId }
|
|
291
|
+
: {};
|
|
274
292
|
}
|
|
275
293
|
|
|
276
294
|
/**
|
package/src/model/featureText.ts
CHANGED
|
@@ -43,6 +43,28 @@
|
|
|
43
43
|
* the same answer typeset — same value, same precision rule, same
|
|
44
44
|
* `postfix1000` switch at a thousand, same translated unit, with the digits
|
|
45
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.
|
|
46
68
|
*/
|
|
47
69
|
import type { FeatureDef, FeatureValueDto, FormatOptions } from "@stapel/attributes-react";
|
|
48
70
|
import { featureConfig, featureType, formatFeatureValue } from "@stapel/attributes-react";
|
|
@@ -102,17 +124,42 @@ export function featureUnit(
|
|
|
102
124
|
* because a host passed `"en_US"` would be a worse outcome than an ungrouped
|
|
103
125
|
* number — so the caller falls back rather than the page failing.
|
|
104
126
|
*/
|
|
105
|
-
function
|
|
127
|
+
function localized(
|
|
128
|
+
value: number,
|
|
129
|
+
digits: number,
|
|
130
|
+
locale: string | undefined,
|
|
131
|
+
useGrouping: boolean
|
|
132
|
+
): string | undefined {
|
|
106
133
|
try {
|
|
107
134
|
return new Intl.NumberFormat(locale, {
|
|
108
135
|
minimumFractionDigits: digits,
|
|
109
136
|
maximumFractionDigits: digits,
|
|
137
|
+
useGrouping,
|
|
110
138
|
}).format(value);
|
|
111
139
|
} catch {
|
|
112
140
|
return undefined;
|
|
113
141
|
}
|
|
114
142
|
}
|
|
115
143
|
|
|
144
|
+
/** Digits below which a unitless number is an identifier rather than a
|
|
145
|
+
* quantity — see the module header. */
|
|
146
|
+
const GROUPING_FLOOR = 10_000;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Does this feature measure something?
|
|
150
|
+
*
|
|
151
|
+
* The RAW config, not the translated unit: whether a value is a measurement
|
|
152
|
+
* is a fact about the catalogue, and it must not change because a host's
|
|
153
|
+
* message catalogue happens to resolve a unit key to an empty string.
|
|
154
|
+
*/
|
|
155
|
+
function hasUnit(config: Readonly<Record<string, unknown>>): boolean {
|
|
156
|
+
return (
|
|
157
|
+
str(config["prefix"]).length > 0 ||
|
|
158
|
+
str(config["postfix"]).length > 0 ||
|
|
159
|
+
str(config["postfix1000"]).length > 0
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
116
163
|
/**
|
|
117
164
|
* The one numeric row, typeset — see the module header for what each piece
|
|
118
165
|
* is repairing.
|
|
@@ -150,7 +197,8 @@ function formatNumeric(
|
|
|
150
197
|
if (postfix1000.length > 0 && Math.abs(parsed) >= 1000) {
|
|
151
198
|
const scaled = Number((parsed / 1000).toFixed(digits));
|
|
152
199
|
const body =
|
|
153
|
-
|
|
200
|
+
localized(scaled, decimalsOf(scaled, digits), options?.locale, true) ??
|
|
201
|
+
String(scaled);
|
|
154
202
|
return join(prefix, body, translated(options, postfix1000));
|
|
155
203
|
}
|
|
156
204
|
|
|
@@ -160,7 +208,11 @@ function formatNumeric(
|
|
|
160
208
|
// a year field and the card read "2024.0").
|
|
161
209
|
const value = kind === "int" ? Math.trunc(parsed) : parsed;
|
|
162
210
|
const fraction = kind === "int" ? 0 : digits;
|
|
163
|
-
|
|
211
|
+
// A measurement is grouped; a unitless number is an identifier until it is
|
|
212
|
+
// too big to be one (D307 — the card read "2 024" for a model year).
|
|
213
|
+
const group = hasUnit(config) || Math.abs(value) >= GROUPING_FLOOR;
|
|
214
|
+
const body =
|
|
215
|
+
localized(value, fraction, options?.locale, group) ?? value.toFixed(fraction);
|
|
164
216
|
return join(prefix, body, translated(options, config["postfix"]));
|
|
165
217
|
}
|
|
166
218
|
|