@asteby/storefront-react 0.25.0 → 0.26.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/README.md +35 -2
- package/dist/account-server.cjs +128 -19
- package/dist/account-server.cjs.map +1 -1
- package/dist/account-server.d.cts +67 -4
- package/dist/account-server.d.ts +67 -4
- package/dist/account-server.js +125 -20
- package/dist/account-server.js.map +1 -1
- package/dist/account.cjs +252 -7
- package/dist/account.cjs.map +1 -1
- package/dist/account.css +91 -0
- package/dist/account.d.cts +130 -6
- package/dist/account.d.ts +130 -6
- package/dist/account.js +247 -8
- package/dist/account.js.map +1 -1
- package/dist/checkout.cjs +162 -8
- package/dist/checkout.cjs.map +1 -1
- package/dist/checkout.d.cts +104 -4
- package/dist/checkout.d.ts +104 -4
- package/dist/checkout.js +158 -10
- package/dist/checkout.js.map +1 -1
- package/dist/chunk-Z4C4LKXF.js +25 -0
- package/dist/chunk-Z4C4LKXF.js.map +1 -0
- package/dist/fit-key-B9sYQVwG.d.cts +49 -0
- package/dist/fit-key-B9sYQVwG.d.ts +49 -0
- package/dist/fitment.cjs +54 -9
- package/dist/fitment.cjs.map +1 -1
- package/dist/fitment.d.cts +20 -2
- package/dist/fitment.d.ts +20 -2
- package/dist/fitment.js +54 -10
- package/dist/fitment.js.map +1 -1
- package/dist/settings-types-jxFQFFqP.d.cts +77 -0
- package/dist/settings-types-jxFQFFqP.d.ts +77 -0
- package/dist/subjects-server.cjs +248 -0
- package/dist/subjects-server.cjs.map +1 -0
- package/dist/subjects-server.d.cts +130 -0
- package/dist/subjects-server.d.ts +130 -0
- package/dist/subjects-server.js +217 -0
- package/dist/subjects-server.js.map +1 -0
- package/dist/subjects.cjs +501 -0
- package/dist/subjects.cjs.map +1 -0
- package/dist/subjects.css +166 -0
- package/dist/subjects.d.cts +178 -0
- package/dist/subjects.d.ts +178 -0
- package/dist/subjects.js +465 -0
- package/dist/subjects.js.map +1 -0
- package/package.json +23 -2
- package/dist/types-D7pVK0GK.d.cts +0 -39
- package/dist/types-D7pVK0GK.d.ts +0 -39
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { resolveSubject } from '@asteby/storefront';
|
|
2
|
+
|
|
3
|
+
// src/subjects/fit-key.ts
|
|
4
|
+
var KEY_RE = /^[a-z0-9._+/-]{1,100}$/;
|
|
5
|
+
var escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6
|
+
function subjectFromFitKey(kind, fitKey) {
|
|
7
|
+
if (!fitKey || !KEY_RE.test(fitKey) || !kind.fit_key_template) return null;
|
|
8
|
+
const names = [];
|
|
9
|
+
const pattern = kind.fit_key_template.split(/(\{[a-z][a-z0-9_]*\})/).map((part) => {
|
|
10
|
+
const m = /^\{([a-z][a-z0-9_]*)\}$/.exec(part);
|
|
11
|
+
if (!m) return escape(part);
|
|
12
|
+
names.push(m[1]);
|
|
13
|
+
return "([^/]+)";
|
|
14
|
+
}).join("");
|
|
15
|
+
const match = new RegExp(`^${pattern}$`).exec(fitKey);
|
|
16
|
+
if (!match) return null;
|
|
17
|
+
const values = {};
|
|
18
|
+
names.forEach((n, i) => values[n] = match[i + 1]);
|
|
19
|
+
const r = resolveSubject(kind, values);
|
|
20
|
+
return r.ok && r.subject.fit_key === fitKey ? r.subject : null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { subjectFromFitKey };
|
|
24
|
+
//# sourceMappingURL=chunk-Z4C4LKXF.js.map
|
|
25
|
+
//# sourceMappingURL=chunk-Z4C4LKXF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/subjects/fit-key.ts"],"names":[],"mappings":";;;AAKA,IAAM,MAAA,GAAS,wBAAA;AAEf,IAAM,SAAS,CAAC,CAAA,KAAc,CAAA,CAAE,OAAA,CAAQ,uBAAuB,MAAM,CAAA;AAO9D,SAAS,iBAAA,CAAkB,MAAmB,MAAA,EAA2D;AAC9G,EAAA,IAAI,CAAC,MAAA,IAAU,CAAC,MAAA,CAAO,IAAA,CAAK,MAAM,CAAA,IAAK,CAAC,IAAA,CAAK,gBAAA,EAAkB,OAAO,IAAA;AACtE,EAAA,MAAM,QAAkB,EAAC;AACzB,EAAA,MAAM,OAAA,GAAU,KAAK,gBAAA,CAClB,KAAA,CAAM,uBAAuB,CAAA,CAC7B,GAAA,CAAI,CAAC,IAAA,KAAS;AACb,IAAA,MAAM,CAAA,GAAI,yBAAA,CAA0B,IAAA,CAAK,IAAI,CAAA;AAC7C,IAAA,IAAI,CAAC,CAAA,EAAG,OAAO,MAAA,CAAO,IAAI,CAAA;AAC1B,IAAA,KAAA,CAAM,IAAA,CAAK,CAAA,CAAE,CAAC,CAAE,CAAA;AAChB,IAAA,OAAO,SAAA;AAAA,EACT,CAAC,CAAA,CACA,IAAA,CAAK,EAAE,CAAA;AACV,EAAA,MAAM,KAAA,GAAQ,IAAI,MAAA,CAAO,CAAA,CAAA,EAAI,OAAO,CAAA,CAAA,CAAG,CAAA,CAAE,KAAK,MAAM,CAAA;AACpD,EAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AACnB,EAAA,MAAM,SAAiC,EAAC;AACxC,EAAA,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAA,EAAG,CAAA,KAAO,MAAA,CAAO,CAAC,CAAA,GAAI,KAAA,CAAM,CAAA,GAAI,CAAC,CAAG,CAAA;AACnD,EAAA,MAAM,CAAA,GAAI,cAAA,CAAe,IAAA,EAAM,MAAM,CAAA;AAErC,EAAA,OAAO,EAAE,EAAA,IAAM,CAAA,CAAE,QAAQ,OAAA,KAAY,MAAA,GAAS,EAAE,OAAA,GAAU,IAAA;AAC5D","file":"chunk-Z4C4LKXF.js","sourcesContent":["// The compatibility key of a saved item, read backwards. The guest keeps only `fit_key` in a cookie (never a plate or\n// anything private); to show its label and to filter by it, it is resolved against the kind's catalog again.\nimport { resolveSubject, type ResolvedSubject, type SubjectKind } from '@asteby/storefront'\n\n/** Shape of a key: what `fit_key` can hold (catalog ids and slugs joined by the template's separators). */\nconst KEY_RE = /^[a-z0-9._+/-]{1,100}$/\n\nconst escape = (s: string) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n\n/**\n * Reads a key back into the item it names: the values are taken from the kind's template and validated against the\n * catalog like a saved item. `null` for a key that is not well formed, does not fit the template or does not resolve\n * (stale catalog, tampered cookie). Free (non-catalog) fields come back as their slug, which is all the key kept.\n */\nexport function subjectFromFitKey(kind: SubjectKind, fitKey: string | null | undefined): ResolvedSubject | null {\n if (!fitKey || !KEY_RE.test(fitKey) || !kind.fit_key_template) return null\n const names: string[] = []\n const pattern = kind.fit_key_template\n .split(/(\\{[a-z][a-z0-9_]*\\})/)\n .map((part) => {\n const m = /^\\{([a-z][a-z0-9_]*)\\}$/.exec(part)\n if (!m) return escape(part)\n names.push(m[1]!)\n return '([^/]+)'\n })\n .join('')\n const match = new RegExp(`^${pattern}$`).exec(fitKey)\n if (!match) return null\n const values: Record<string, string> = {}\n names.forEach((n, i) => (values[n] = match[i + 1]!))\n const r = resolveSubject(kind, values)\n // The key must be exactly the one the server would compute for those values.\n return r.ok && r.subject.fit_key === fitKey ? r.subject : null\n}\n"]}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as _asteby_storefront from '@asteby/storefront';
|
|
2
|
+
import { SubjectProblemCode, SubjectKind, ResolvedSubject } from '@asteby/storefront';
|
|
3
|
+
|
|
4
|
+
/** What the buyer is doing. It is the `intent` field of the form. */
|
|
5
|
+
/** `subject-refine` (without JavaScript) only echoes the values so the form shows the next level of the cascade. */
|
|
6
|
+
type SubjectIntent = 'subject-save' | 'subject-delete' | 'subject-default' | 'subject-refine';
|
|
7
|
+
type SubjectErrorCode =
|
|
8
|
+
/** A field was rejected (`error.field` says which, `fieldErrors` carries the code per field). */
|
|
9
|
+
'validation'
|
|
10
|
+
/** The item is already saved. */
|
|
11
|
+
| 'duplicate'
|
|
12
|
+
/** The kind's limit was reached. */
|
|
13
|
+
| 'limit' | 'rate_limited'
|
|
14
|
+
/** No session. */
|
|
15
|
+
| 'unauthorized'
|
|
16
|
+
/** The item is not the buyer's (or no longer exists). */
|
|
17
|
+
| 'not_found'
|
|
18
|
+
/** The store declares no such kind. */
|
|
19
|
+
| 'unsupported'
|
|
20
|
+
/** The store's own credential lacks a permission. */
|
|
21
|
+
| 'store_misconfigured'
|
|
22
|
+
/** Anything else (network, server). */
|
|
23
|
+
| 'unavailable';
|
|
24
|
+
/** What the action returns. Texts are NOT here: the components write them (i18n). Never carries a private value. */
|
|
25
|
+
type SubjectActionData = {
|
|
26
|
+
ok: boolean;
|
|
27
|
+
intent?: SubjectIntent;
|
|
28
|
+
/** The item the form was about (`id` of an edit); absent for a new one. Lets the page reopen the right form after an error. */
|
|
29
|
+
id?: string;
|
|
30
|
+
/** The item after a save. */
|
|
31
|
+
subject?: _asteby_storefront.Subject;
|
|
32
|
+
/** Public values the buyer typed, to refill the form after an error or a refinement (never a private one). */
|
|
33
|
+
values?: Record<string, string>;
|
|
34
|
+
/** Per form field name (`attributes.make`) → why. */
|
|
35
|
+
fieldErrors?: Record<string, SubjectProblemCode | 'invalid'>;
|
|
36
|
+
error?: {
|
|
37
|
+
code: SubjectErrorCode;
|
|
38
|
+
field?: string;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Reads a key back into the item it names: the values are taken from the kind's template and validated against the
|
|
44
|
+
* catalog like a saved item. `null` for a key that is not well formed, does not fit the template or does not resolve
|
|
45
|
+
* (stale catalog, tampered cookie). Free (non-catalog) fields come back as their slug, which is all the key kept.
|
|
46
|
+
*/
|
|
47
|
+
declare function subjectFromFitKey(kind: SubjectKind, fitKey: string | null | undefined): ResolvedSubject | null;
|
|
48
|
+
|
|
49
|
+
export { type SubjectActionData as S, type SubjectErrorCode as a, type SubjectIntent as b, subjectFromFitKey as s };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as _asteby_storefront from '@asteby/storefront';
|
|
2
|
+
import { SubjectProblemCode, SubjectKind, ResolvedSubject } from '@asteby/storefront';
|
|
3
|
+
|
|
4
|
+
/** What the buyer is doing. It is the `intent` field of the form. */
|
|
5
|
+
/** `subject-refine` (without JavaScript) only echoes the values so the form shows the next level of the cascade. */
|
|
6
|
+
type SubjectIntent = 'subject-save' | 'subject-delete' | 'subject-default' | 'subject-refine';
|
|
7
|
+
type SubjectErrorCode =
|
|
8
|
+
/** A field was rejected (`error.field` says which, `fieldErrors` carries the code per field). */
|
|
9
|
+
'validation'
|
|
10
|
+
/** The item is already saved. */
|
|
11
|
+
| 'duplicate'
|
|
12
|
+
/** The kind's limit was reached. */
|
|
13
|
+
| 'limit' | 'rate_limited'
|
|
14
|
+
/** No session. */
|
|
15
|
+
| 'unauthorized'
|
|
16
|
+
/** The item is not the buyer's (or no longer exists). */
|
|
17
|
+
| 'not_found'
|
|
18
|
+
/** The store declares no such kind. */
|
|
19
|
+
| 'unsupported'
|
|
20
|
+
/** The store's own credential lacks a permission. */
|
|
21
|
+
| 'store_misconfigured'
|
|
22
|
+
/** Anything else (network, server). */
|
|
23
|
+
| 'unavailable';
|
|
24
|
+
/** What the action returns. Texts are NOT here: the components write them (i18n). Never carries a private value. */
|
|
25
|
+
type SubjectActionData = {
|
|
26
|
+
ok: boolean;
|
|
27
|
+
intent?: SubjectIntent;
|
|
28
|
+
/** The item the form was about (`id` of an edit); absent for a new one. Lets the page reopen the right form after an error. */
|
|
29
|
+
id?: string;
|
|
30
|
+
/** The item after a save. */
|
|
31
|
+
subject?: _asteby_storefront.Subject;
|
|
32
|
+
/** Public values the buyer typed, to refill the form after an error or a refinement (never a private one). */
|
|
33
|
+
values?: Record<string, string>;
|
|
34
|
+
/** Per form field name (`attributes.make`) → why. */
|
|
35
|
+
fieldErrors?: Record<string, SubjectProblemCode | 'invalid'>;
|
|
36
|
+
error?: {
|
|
37
|
+
code: SubjectErrorCode;
|
|
38
|
+
field?: string;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Reads a key back into the item it names: the values are taken from the kind's template and validated against the
|
|
44
|
+
* catalog like a saved item. `null` for a key that is not well formed, does not fit the template or does not resolve
|
|
45
|
+
* (stale catalog, tampered cookie). Free (non-catalog) fields come back as their slug, which is all the key kept.
|
|
46
|
+
*/
|
|
47
|
+
declare function subjectFromFitKey(kind: SubjectKind, fitKey: string | null | undefined): ResolvedSubject | null;
|
|
48
|
+
|
|
49
|
+
export { type SubjectActionData as S, type SubjectErrorCode as a, type SubjectIntent as b, subjectFromFitKey as s };
|
package/dist/fitment.cjs
CHANGED
|
@@ -35,6 +35,32 @@ function makeMeta(m, aliases) {
|
|
|
35
35
|
function labelFromId(id) {
|
|
36
36
|
return id.split("-").filter(Boolean).join(" ").toUpperCase();
|
|
37
37
|
}
|
|
38
|
+
function parseEngine(e) {
|
|
39
|
+
if (typeof e === "string") return isText(e) ? { id: e, label: labelFromId(e) } : null;
|
|
40
|
+
if (Array.isArray(e)) {
|
|
41
|
+
const [id, ...years] = e;
|
|
42
|
+
if (!isText(id)) return null;
|
|
43
|
+
const ranges2 = [];
|
|
44
|
+
for (let i = 0; i + 1 < years.length; i += 2) {
|
|
45
|
+
const a = years[i];
|
|
46
|
+
const b = years[i + 1];
|
|
47
|
+
if (isNum(a) && isNum(b)) ranges2.push([a, b]);
|
|
48
|
+
}
|
|
49
|
+
const first = ranges2[0];
|
|
50
|
+
const last = ranges2[ranges2.length - 1];
|
|
51
|
+
return first && last ? { id, label: labelFromId(id), from: first[0], to: last[1], ranges: ranges2 } : { id, label: labelFromId(id) };
|
|
52
|
+
}
|
|
53
|
+
const o = e;
|
|
54
|
+
if (!o || !isText(o.id)) return null;
|
|
55
|
+
const ranges = (Array.isArray(o.ranges) ? o.ranges : []).filter((r) => Array.isArray(r) && isNum(r[0]) && isNum(r[1]));
|
|
56
|
+
return {
|
|
57
|
+
id: o.id,
|
|
58
|
+
label: isText(o.label) ? o.label : labelFromId(o.id),
|
|
59
|
+
...isNum(o.from) ? { from: o.from } : {},
|
|
60
|
+
...isNum(o.to) ? { to: o.to } : {},
|
|
61
|
+
...ranges.length ? { ranges } : {}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
38
64
|
function parseCatalog(raw, opts = {}) {
|
|
39
65
|
let makesRaw = raw?.makes;
|
|
40
66
|
if (typeof makesRaw === "string") {
|
|
@@ -52,12 +78,7 @@ function parseCatalog(raw, opts = {}) {
|
|
|
52
78
|
for (const entry of m.models) {
|
|
53
79
|
const mo = Array.isArray(entry) ? { id: entry[0], from: entry[1], to: entry[2], engines: entry[3] } : entry;
|
|
54
80
|
if (!mo || !isText(mo.id) || !isNum(mo.from) || !isNum(mo.to) || !Array.isArray(mo.engines)) continue;
|
|
55
|
-
const engines = mo.engines.map(
|
|
56
|
-
id: e.id,
|
|
57
|
-
label: isText(e.label) ? e.label : labelFromId(e.id),
|
|
58
|
-
...isNum(e.from) ? { from: e.from } : {},
|
|
59
|
-
...isNum(e.to) ? { to: e.to } : {}
|
|
60
|
-
}));
|
|
81
|
+
const engines = mo.engines.map(parseEngine).filter((e) => e !== null);
|
|
61
82
|
if (engines.length) models.push({ id: mo.id, label: isText(mo.label) ? mo.label : labelFromId(mo.id), from: mo.from, to: mo.to, engines });
|
|
62
83
|
}
|
|
63
84
|
if (models.length || m.models.length === 0) makes.push({ id: m.id, label: m.label, models, ...makeMeta(m, aliases) });
|
|
@@ -70,8 +91,8 @@ function vehicleKey(v) {
|
|
|
70
91
|
function fitTag(key, config = DEFAULT_FITMENT_CONFIG) {
|
|
71
92
|
return `${config.tagPrefix}${key}`;
|
|
72
93
|
}
|
|
73
|
-
var engineCovers = (e, model, year) => year >= (e.from ?? model.from) && year <= (e.to ?? model.to);
|
|
74
|
-
var modelCovers = (m, year) => year == null ||
|
|
94
|
+
var engineCovers = (e, model, year) => e.ranges ? e.ranges.some(([a, b]) => year >= a && year <= b) : year >= (e.from ?? model.from) && year <= (e.to ?? model.to);
|
|
95
|
+
var modelCovers = (m, year) => year == null || m.engines.some((e) => engineCovers(e, m, year));
|
|
75
96
|
function num(v) {
|
|
76
97
|
const n = Number(v);
|
|
77
98
|
return v !== "" && Number.isInteger(n) ? n : null;
|
|
@@ -112,7 +133,7 @@ function yearOptions(catalog, draft) {
|
|
|
112
133
|
const from = Math.min(...models.map((m) => m.from));
|
|
113
134
|
const to = Math.max(...models.map((m) => m.to));
|
|
114
135
|
const years = [];
|
|
115
|
-
for (let y = to; y >= from; y--) years.push({ value: String(y), label: String(y) });
|
|
136
|
+
for (let y = to; y >= from; y--) if (models.some((m) => modelCovers(m, y))) years.push({ value: String(y), label: String(y) });
|
|
116
137
|
return years;
|
|
117
138
|
}
|
|
118
139
|
function modelOptions(catalog, draft) {
|
|
@@ -210,6 +231,29 @@ function withoutQueryParam(path, param) {
|
|
|
210
231
|
url.searchParams.delete(param);
|
|
211
232
|
return `${url.pathname}${url.search}`;
|
|
212
233
|
}
|
|
234
|
+
function vehicleCatalogToTree(catalog) {
|
|
235
|
+
const tree = {};
|
|
236
|
+
for (const make of catalog.makes) {
|
|
237
|
+
if (make.models.length === 0) continue;
|
|
238
|
+
const models = {};
|
|
239
|
+
for (const model of make.models) {
|
|
240
|
+
const engines = {};
|
|
241
|
+
let from = Infinity;
|
|
242
|
+
let to = -Infinity;
|
|
243
|
+
for (const e of model.engines) {
|
|
244
|
+
const spans = e.ranges?.length ? e.ranges : [[e.from ?? model.from, e.to ?? model.to]];
|
|
245
|
+
for (const [a, b] of spans) {
|
|
246
|
+
from = Math.min(from, a);
|
|
247
|
+
to = Math.max(to, b);
|
|
248
|
+
}
|
|
249
|
+
engines[e.id] = e.ranges?.length ? { label: e.label, ranges: e.ranges } : { label: e.label, from: e.from ?? model.from, to: e.to ?? model.to };
|
|
250
|
+
}
|
|
251
|
+
models[model.id] = { label: model.label, from: Number.isFinite(from) ? from : model.from, to: Number.isFinite(to) ? to : model.to, children: engines };
|
|
252
|
+
}
|
|
253
|
+
tree[make.id] = { label: make.label, children: models };
|
|
254
|
+
}
|
|
255
|
+
return tree;
|
|
256
|
+
}
|
|
213
257
|
|
|
214
258
|
exports.DEFAULT_FITMENT_CONFIG = DEFAULT_FITMENT_CONFIG;
|
|
215
259
|
exports.EMPTY_DRAFT = EMPTY_DRAFT;
|
|
@@ -231,6 +275,7 @@ exports.resolveVehicle = resolveVehicle;
|
|
|
231
275
|
exports.rowCoversYear = rowCoversYear;
|
|
232
276
|
exports.safeRedirect = safeRedirect;
|
|
233
277
|
exports.splitModelValue = splitModelValue;
|
|
278
|
+
exports.vehicleCatalogToTree = vehicleCatalogToTree;
|
|
234
279
|
exports.vehicleFromDraft = vehicleFromDraft;
|
|
235
280
|
exports.vehicleKey = vehicleKey;
|
|
236
281
|
exports.vehicleLongName = vehicleLongName;
|
package/dist/fitment.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/fitment/fitment.ts"],"names":["model"],"mappings":"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","file":"fitment.cjs","sourcesContent":["// Vehicle fitment (\"Mi vehículo\"): a make → model → year → engine catalog, vehicle keys, the\n// cascading selector options and the fit state of a product. Pure functions with no UI, no\n// storage and no copy: every tag prefix, attribute name and legacy spelling is injectable through\n// `FitmentConfig`, so any store that sells parts can use it. Nothing here decides on its own\n// whether a part fits a vehicle: it only compares what the backend (OPS) published.\n\nexport type VehicleEngine = { id: string; label: string; from?: number; to?: number }\nexport type VehicleModel = { id: string; label: string; from: number; to: number; engines: VehicleEngine[] }\nexport type MakePriority = 'high' | 'medium' | 'low'\n/**\n * A make of the catalog. `models` may be empty while the catalog is being imported: the make is\n * declared but it does not show in the selector until it has models.\n */\nexport type VehicleMake = {\n id: string\n label: string\n models: VehicleModel[]\n priority?: MakePriority\n /** Free-form segment of the make (e.g. light / heavy duty). Passed through as published. */\n kind?: string\n refs?: number\n observed_from?: number\n observed_to?: number\n typical_from?: number\n typical_to?: number\n}\nexport type VehicleCatalog = { makes: VehicleMake[] }\n\n/** Partial selection of the selector (all text, as it arrives from a form). */\nexport type VehicleDraft = { make: string; year: string; model: string; engine: string }\n\n/** A complete vehicle resolved against the catalog (with labels to display). */\nexport type Vehicle = {\n key: string\n make: { id: string; label: string }\n model: { id: string; label: string }\n year: number\n engine: { id: string; label: string }\n}\n\nexport type Option = { value: string; label: string }\n\n/** Fit state of a product for a vehicle. */\nexport type Fit = 'fits' | 'universal' | 'no' | 'unknown'\n\nexport type FitmentConfig = {\n /** Prefix of the tags that publish compatibility: `<prefix><vehicle key>`. Default `fit:`. */\n tagPrefix: string\n /** Tag that means \"fits everything\". Default `<tagPrefix>universal`. */\n universalTag: string\n /** Prefix of the single resolved-state tag written by `withFit`. Default `fit-state:`. */\n stateTagPrefix: string\n /** Product attribute holding the compatibility table. Default `compatibility`. */\n compatAttribute: string\n /** Separators of the compact compatibility table. */\n rowSeparator: string\n cellSeparator: string\n}\n\nexport const DEFAULT_FITMENT_CONFIG: FitmentConfig = {\n tagPrefix: 'fit:',\n universalTag: 'fit:universal',\n stateTagPrefix: 'fit-state:',\n compatAttribute: 'compatibility',\n rowSeparator: ';',\n cellSeparator: '|',\n}\n\n/** Merges overrides; an unset `universalTag` follows a custom `tagPrefix`. */\nexport function fitmentConfig(overrides: Partial<FitmentConfig> = {}): FitmentConfig {\n const tagPrefix = overrides.tagPrefix ?? DEFAULT_FITMENT_CONFIG.tagPrefix\n return {\n ...DEFAULT_FITMENT_CONFIG,\n ...overrides,\n tagPrefix,\n universalTag: overrides.universalTag ?? `${tagPrefix}universal`,\n }\n}\n\nexport const EMPTY_DRAFT: VehicleDraft = { make: '', year: '', model: '', engine: '' }\n\nconst isNum = (v: unknown): v is number => typeof v === 'number' && Number.isFinite(v)\nconst isText = (v: unknown): v is string => typeof v === 'string' && v.length > 0\n\nexport type ParseCatalogOptions = {\n /** Maps the backend's spelling of a priority to the canonical one (e.g. `{ alta: 'high' }`). English values always work. */\n priorityAliases?: Record<string, MakePriority>\n}\n\nfunction makeMeta(m: Record<string, unknown>, aliases: Record<string, MakePriority>): Partial<VehicleMake> {\n const raw = typeof m.priority === 'string' ? m.priority.toLowerCase() : ''\n const priority = aliases[raw] ?? raw\n const out: Partial<VehicleMake> = {}\n if (priority === 'high' || priority === 'medium' || priority === 'low') out.priority = priority\n if (isText(m.kind)) out.kind = m.kind\n for (const k of ['refs', 'observed_from', 'observed_to', 'typical_from', 'typical_to'] as const) {\n if (isNum(m[k])) out[k] = m[k] as number\n }\n return out\n}\n\n/** \"np300-frontier\" → \"NP300 FRONTIER\"; \"1.9-tdi\" → \"1.9 TDI\". */\nfunction labelFromId(id: string): string {\n return id.split('-').filter(Boolean).join(' ').toUpperCase()\n}\n\n/**\n * Reads the vehicle catalog from the props of a content section; drops whatever has the wrong shape.\n * Accepts the compact format (a real catalog weighs tens of KB and OPS caps each write): a model may\n * be a tuple `[id, from, to, engines]`, omit `label` (derived from the id) and list engines as ids.\n * Props stored as text (`makes` as a JSON string) are parsed.\n */\nexport function parseCatalog(raw: unknown, opts: ParseCatalogOptions = {}): VehicleCatalog {\n let makesRaw = (raw as { makes?: unknown } | null | undefined)?.makes\n if (typeof makesRaw === 'string') {\n try {\n makesRaw = JSON.parse(makesRaw)\n } catch {\n makesRaw = []\n }\n }\n const aliases = opts.priorityAliases ?? {}\n const makes: VehicleMake[] = []\n for (const m of Array.isArray(makesRaw) ? makesRaw : []) {\n if (!m || !isText(m.id) || !isText(m.label) || !Array.isArray(m.models)) continue\n const models: VehicleModel[] = []\n for (const entry of m.models) {\n const mo = Array.isArray(entry) ? { id: entry[0], from: entry[1], to: entry[2], engines: entry[3] } : entry\n if (!mo || !isText(mo.id) || !isNum(mo.from) || !isNum(mo.to) || !Array.isArray(mo.engines)) continue\n const engines: VehicleEngine[] = mo.engines\n .map((e: unknown) => (typeof e === 'string' ? { id: e, label: labelFromId(e) } : e))\n .filter((e: VehicleEngine) => e && isText(e.id))\n .map((e: VehicleEngine) => ({\n id: e.id,\n label: isText(e.label) ? e.label : labelFromId(e.id),\n ...(isNum(e.from) ? { from: e.from } : {}),\n ...(isNum(e.to) ? { to: e.to } : {}),\n }))\n if (engines.length) models.push({ id: mo.id, label: isText(mo.label) ? mo.label : labelFromId(mo.id), from: mo.from, to: mo.to, engines })\n }\n // A make declared without models (waiting for the import) is kept; one with only invalid models is dropped.\n if (models.length || m.models.length === 0) makes.push({ id: m.id, label: m.label, models, ...makeMeta(m, aliases) })\n }\n return { makes }\n}\n\nexport function vehicleKey(v: { make: string; model: string; year: number | string; engine: string }): string {\n return `${v.make}/${v.model}/${v.year}/${v.engine}`\n}\n\n/** Tag that marks a part as fitting the vehicle with this key. */\nexport function fitTag(key: string, config: FitmentConfig = DEFAULT_FITMENT_CONFIG): string {\n return `${config.tagPrefix}${key}`\n}\n\nconst engineCovers = (e: VehicleEngine, model: VehicleModel, year: number) =>\n year >= (e.from ?? model.from) && year <= (e.to ?? model.to)\n\nconst modelCovers = (m: VehicleModel, year: number | null) => year == null || (year >= m.from && year <= m.to)\n\nfunction num(v: string): number | null {\n const n = Number(v)\n return v !== '' && Number.isInteger(n) ? n : null\n}\n\n/** Resolves a `make/model/year/engine` key against the catalog; null when it does not exist. */\nexport function resolveVehicle(catalog: VehicleCatalog, key: string | null | undefined): Vehicle | null {\n if (!key) return null\n const [makeId, modelId, yearText, engineId] = key.split('/')\n const year = num(yearText ?? '')\n const make = catalog.makes.find((m) => m.id === makeId)\n const model = make?.models.find((m) => m.id === modelId)\n if (!make || !model || year == null || !modelCovers(model, year)) return null\n const engine = model.engines.find((e) => e.id === engineId && engineCovers(e, model, year))\n if (!engine) return null\n return {\n key: vehicleKey({ make: make.id, model: model.id, year, engine: engine.id }),\n make: { id: make.id, label: make.label },\n model: { id: model.id, label: model.label },\n year,\n engine: { id: engine.id, label: engine.label },\n }\n}\n\n/** Short name for chips and badges: \"Make Model 2018\". */\nexport function vehicleName(v: Vehicle): string {\n return `${v.make.label} ${v.model.label} ${v.year}`\n}\n\n/** With engine: \"Make Model 2018 · 1.6 L\". */\nexport function vehicleLongName(v: Vehicle): string {\n return `${vehicleName(v)} · ${v.engine.label}`\n}\n\n// --- Cascade make → year → model → engine ------------------------------------------------------\n// Every list filters by what is already chosen, in any order. With nothing chosen they show\n// everything (so the form also works without JavaScript).\n\nfunction modelsOf(catalog: VehicleCatalog, draft: Pick<VehicleDraft, 'make' | 'year'>): { make: VehicleMake; model: VehicleModel }[] {\n const year = num(draft.year)\n return catalog.makes\n .filter((m) => !draft.make || m.id === draft.make)\n .flatMap((make) => make.models.filter((m) => modelCovers(m, year)).map((model) => ({ make, model })))\n}\n\nexport function makeOptions(catalog: VehicleCatalog): Option[] {\n return catalog.makes.filter((m) => m.models.length > 0).map((m) => ({ value: m.id, label: m.label }))\n}\n\nexport function yearOptions(catalog: VehicleCatalog, draft: Pick<VehicleDraft, 'make' | 'model'>): Option[] {\n const models = catalog.makes\n .filter((m) => !draft.make || m.id === draft.make)\n .flatMap((m) => m.models)\n .filter((m) => !draft.model || m.id === draft.model)\n if (models.length === 0) return []\n const from = Math.min(...models.map((m) => m.from))\n const to = Math.max(...models.map((m) => m.to))\n const years: Option[] = []\n for (let y = to; y >= from; y--) years.push({ value: String(y), label: String(y) })\n return years\n}\n\n/**\n * Model options. Model ids repeat across makes (`pick-up` exists in several): with no make chosen\n * the value is `make/model` (unique) and `splitModelValue` sets the make when it is picked; with a\n * make chosen the value is the model id.\n */\nexport function modelOptions(catalog: VehicleCatalog, draft: Pick<VehicleDraft, 'make' | 'year'>): Option[] {\n return modelsOf(catalog, draft).map(({ make, model }) => ({\n value: draft.make ? model.id : `${make.id}/${model.id}`,\n label: draft.make ? model.label : `${make.label} ${model.label}`,\n }))\n}\n\n/** `make/model` (value of the make-less list) → { make, model }; a bare id returns only the model. */\nexport function splitModelValue(value: string): { make?: string; model: string } {\n const i = value.indexOf('/')\n return i === -1 ? { model: value } : { make: value.slice(0, i), model: value.slice(i + 1) }\n}\n\nexport function engineOptions(catalog: VehicleCatalog, draft: Pick<VehicleDraft, 'make' | 'year' | 'model'>): Option[] {\n const year = num(draft.year)\n const seen = new Map<string, string>()\n for (const { model } of modelsOf(catalog, draft).filter(({ model }) => !draft.model || model.id === draft.model)) {\n for (const e of model.engines) {\n if (year == null || engineCovers(e, model, year)) seen.set(e.id, e.label)\n }\n }\n return [...seen].map(([value, label]) => ({ value, label }))\n}\n\n/** Drops what was chosen and is no longer valid after changing another field. */\nexport function reconcileDraft(catalog: VehicleCatalog, draft: VehicleDraft): VehicleDraft {\n let next = { ...draft }\n if (next.year && !yearOptions(catalog, { make: next.make, model: '' }).some((o) => o.value === next.year)) next = { ...next, year: '' }\n // A bare model id sets the make only when it belongs to a single one; repeated ids are ambiguous and dropped.\n if (next.model && !next.make) {\n const owners = catalog.makes.filter((m) => m.models.some((mo) => mo.id === next.model))\n const [owner] = owners\n if (owners.length === 1 && owner) next = { ...next, make: owner.id }\n }\n if (next.model && !modelOptions(catalog, next).some((o) => o.value === next.model)) next = { ...next, model: '' }\n if (next.engine && !engineOptions(catalog, next).some((o) => o.value === next.engine)) next = { ...next, engine: '' }\n return next\n}\n\nexport function draftFromVehicle(v: Vehicle | null): VehicleDraft {\n return v ? { make: v.make.id, year: String(v.year), model: v.model.id, engine: v.engine.id } : EMPTY_DRAFT\n}\n\n/** Complete vehicle from the form, validated against the catalog. */\nexport function vehicleFromDraft(catalog: VehicleCatalog, draft: VehicleDraft): Vehicle | null {\n return resolveVehicle(catalog, vehicleKey({ make: draft.make, model: draft.model, year: draft.year, engine: draft.engine }))\n}\n\n// --- Popular vehicles --------------------------------------------------------------------------\n// They come from the data: makes with `high` priority in the catalog, most references first.\n\nexport function popularMakes(catalog: VehicleCatalog): VehicleMake[] {\n return catalog.makes.filter((m) => m.priority === 'high' && m.models.length > 0).sort((a, b) => (b.refs ?? 0) - (a.refs ?? 0))\n}\n\n/**\n * One shortcut per popular make: its first model in a typical year (the middle of the make's typical\n * range, clamped to the model's range) with the first engine that covers that year.\n */\nexport function popularVehicles(catalog: VehicleCatalog, limit = 6): { key: string; label: string }[] {\n const out: { key: string; label: string }[] = []\n for (const make of popularMakes(catalog)) {\n if (out.length >= limit) break\n const model = make.models[0]\n if (!model) continue\n const mid = make.typical_from != null && make.typical_to != null ? Math.round((make.typical_from + make.typical_to) / 2) : model.to\n const year = Math.min(model.to, Math.max(model.from, mid))\n const engine = model.engines.find((e) => engineCovers(e, model, year))\n if (!engine) continue\n out.push({ key: vehicleKey({ make: make.id, model: model.id, year, engine: engine.id }), label: `${make.label} ${model.label} ${year}` })\n }\n return out\n}\n\n// --- Fitment -----------------------------------------------------------------------------------\n\ntype WithTags = { tags: readonly string[] }\ntype WithAttributes = { attributes: Record<string, unknown> }\n\n/**\n * Does the part fit the vehicle? Only compares what the backend published: a `<prefix><key>` tag\n * (fits), the universal tag (fits everything) or, when the part has other fit tags, no fit. With no\n * fitment data nothing is asserted (\"unknown\").\n */\nexport function fitStatus(product: WithTags, vehicle: { key: string } | null, config: FitmentConfig = DEFAULT_FITMENT_CONFIG): Fit {\n const fits = product.tags.filter((t) => t.startsWith(config.tagPrefix))\n if (fits.length === 0) return 'unknown'\n if (fits.includes(config.universalTag)) return 'universal'\n if (!vehicle) return 'unknown'\n return fits.includes(fitTag(vehicle.key, config)) ? 'fits' : 'no'\n}\n\n/** Values for a `filter.tag` query that lists only what fits (the API takes them as \"any of\"). */\nexport function fitFilterTags(vehicle: { key: string }, config: FitmentConfig = DEFAULT_FITMENT_CONFIG): string[] {\n return [fitTag(vehicle.key, config), config.universalTag]\n}\n\nexport type CompatRow = { make: string; model: string; years: string; engine: string; note: string | null }\n\n/** Compatibility table: the attribute holds \"Make|Model|Years|Engine|Note;Make|…\". */\nexport function compatRows(product: WithAttributes, config: FitmentConfig = DEFAULT_FITMENT_CONFIG): CompatRow[] {\n const raw = product.attributes[config.compatAttribute]\n if (typeof raw !== 'string' || !raw) return []\n return raw\n .split(config.rowSeparator)\n .map((row) => row.split(config.cellSeparator).map((c) => c.trim()))\n .flatMap((c): CompatRow[] => {\n const [make, model, years, engine, note] = c\n return c.length >= 4 && make && model ? [{ make, model, years: years ?? '', engine: engine ?? '', note: note || null }] : []\n })\n}\n\n/** \"2012-2019\" contains the year (a bare \"2016\" too). */\nexport function rowCoversYear(years: string, year: number): boolean {\n const [from, to] = years.split(/[–-]/).map((s) => Number(s.trim()))\n if (from === undefined || !Number.isFinite(from)) return false\n return year >= from && year <= (to !== undefined && Number.isFinite(to) ? to : from)\n}\n\n/**\n * The backend publishes one tag per compatible year and engine (hundreds per part). Loaders replace\n * them with ONE `<stateTagPrefix><state>` tag, resolved for the chosen vehicle, so they are not\n * serialized into the page.\n */\nexport function withFit<T extends WithTags>(product: T, vehicle: { key: string } | null, config: FitmentConfig = DEFAULT_FITMENT_CONFIG): T {\n const state = fitStatus(product, vehicle, config)\n return { ...product, tags: [...product.tags.filter((t) => !t.startsWith(config.tagPrefix)), `${config.stateTagPrefix}${state}`] }\n}\n\n/** Fit state already resolved by `withFit` (\"unknown\" if the product did not go through it). */\nexport function fitState(product: WithTags, config: FitmentConfig = DEFAULT_FITMENT_CONFIG): Fit {\n const tag = product.tags.find((t) => t.startsWith(config.stateTagPrefix))\n const value = tag?.slice(config.stateTagPrefix.length)\n return value === 'fits' || value === 'universal' || value === 'no' ? value : 'unknown'\n}\n\n/** Validates a redirect target: only relative paths of the own site. */\nexport function safeRedirect(target: string | null | undefined, fallback = '/'): string {\n return typeof target === 'string' && target.startsWith('/') && !target.startsWith('//') && !target.includes('\\\\') ? target : fallback\n}\n\n/** Removes the selector's error flag (default `?vehicle=invalid`) from a path so it can be returned to clean. */\nexport function withoutQueryParam(path: string, param: string): string {\n const url = new URL(path, 'http://local.invalid')\n url.searchParams.delete(param)\n return `${url.pathname}${url.search}`\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/fitment/fitment.ts"],"names":["ranges","model"],"mappings":"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","file":"fitment.cjs","sourcesContent":["// Vehicle fitment (\"Mi vehículo\"): a make → model → year → engine catalog, vehicle keys, the\n// cascading selector options and the fit state of a product. Pure functions with no UI, no\n// storage and no copy: every tag prefix, attribute name and legacy spelling is injectable through\n// `FitmentConfig`, so any store that sells parts can use it. Nothing here decides on its own\n// whether a part fits a vehicle: it only compares what the backend (OPS) published.\n\nimport type { SubjectCatalog, SubjectCatalogNode } from '@asteby/storefront'\n\n/**\n * `ranges` are the stretches of model years in which the engine has parts ([from, to] pairs; a gap is a real gap).\n * Without them the engine covers `from`–`to`, or the whole range of its model.\n */\nexport type VehicleEngine = { id: string; label: string; from?: number; to?: number; ranges?: [number, number][] }\nexport type VehicleModel = { id: string; label: string; from: number; to: number; engines: VehicleEngine[] }\nexport type MakePriority = 'high' | 'medium' | 'low'\n/**\n * A make of the catalog. `models` may be empty while the catalog is being imported: the make is\n * declared but it does not show in the selector until it has models.\n */\nexport type VehicleMake = {\n id: string\n label: string\n models: VehicleModel[]\n priority?: MakePriority\n /** Free-form segment of the make (e.g. light / heavy duty). Passed through as published. */\n kind?: string\n refs?: number\n observed_from?: number\n observed_to?: number\n typical_from?: number\n typical_to?: number\n}\nexport type VehicleCatalog = { makes: VehicleMake[] }\n\n/** Partial selection of the selector (all text, as it arrives from a form). */\nexport type VehicleDraft = { make: string; year: string; model: string; engine: string }\n\n/** A complete vehicle resolved against the catalog (with labels to display). */\nexport type Vehicle = {\n key: string\n make: { id: string; label: string }\n model: { id: string; label: string }\n year: number\n engine: { id: string; label: string }\n}\n\nexport type Option = { value: string; label: string }\n\n/** Fit state of a product for a vehicle. */\nexport type Fit = 'fits' | 'universal' | 'no' | 'unknown'\n\nexport type FitmentConfig = {\n /** Prefix of the tags that publish compatibility: `<prefix><vehicle key>`. Default `fit:`. */\n tagPrefix: string\n /** Tag that means \"fits everything\". Default `<tagPrefix>universal`. */\n universalTag: string\n /** Prefix of the single resolved-state tag written by `withFit`. Default `fit-state:`. */\n stateTagPrefix: string\n /** Product attribute holding the compatibility table. Default `compatibility`. */\n compatAttribute: string\n /** Separators of the compact compatibility table. */\n rowSeparator: string\n cellSeparator: string\n}\n\nexport const DEFAULT_FITMENT_CONFIG: FitmentConfig = {\n tagPrefix: 'fit:',\n universalTag: 'fit:universal',\n stateTagPrefix: 'fit-state:',\n compatAttribute: 'compatibility',\n rowSeparator: ';',\n cellSeparator: '|',\n}\n\n/** Merges overrides; an unset `universalTag` follows a custom `tagPrefix`. */\nexport function fitmentConfig(overrides: Partial<FitmentConfig> = {}): FitmentConfig {\n const tagPrefix = overrides.tagPrefix ?? DEFAULT_FITMENT_CONFIG.tagPrefix\n return {\n ...DEFAULT_FITMENT_CONFIG,\n ...overrides,\n tagPrefix,\n universalTag: overrides.universalTag ?? `${tagPrefix}universal`,\n }\n}\n\nexport const EMPTY_DRAFT: VehicleDraft = { make: '', year: '', model: '', engine: '' }\n\nconst isNum = (v: unknown): v is number => typeof v === 'number' && Number.isFinite(v)\nconst isText = (v: unknown): v is string => typeof v === 'string' && v.length > 0\n\nexport type ParseCatalogOptions = {\n /** Maps the backend's spelling of a priority to the canonical one (e.g. `{ alta: 'high' }`). English values always work. */\n priorityAliases?: Record<string, MakePriority>\n}\n\nfunction makeMeta(m: Record<string, unknown>, aliases: Record<string, MakePriority>): Partial<VehicleMake> {\n const raw = typeof m.priority === 'string' ? m.priority.toLowerCase() : ''\n const priority = aliases[raw] ?? raw\n const out: Partial<VehicleMake> = {}\n if (priority === 'high' || priority === 'medium' || priority === 'low') out.priority = priority\n if (isText(m.kind)) out.kind = m.kind\n for (const k of ['refs', 'observed_from', 'observed_to', 'typical_from', 'typical_to'] as const) {\n if (isNum(m[k])) out[k] = m[k] as number\n }\n return out\n}\n\n/** \"np300-frontier\" → \"NP300 FRONTIER\"; \"1.9-tdi\" → \"1.9 TDI\". */\nfunction labelFromId(id: string): string {\n return id.split('-').filter(Boolean).join(' ').toUpperCase()\n}\n\n/**\n * One engine of the catalog: `\"2.4\"` (the whole range of its model), `[\"2.4\", 2007, 2017, 2020, 2022]` (an id and pairs of\n * years: stretches with parts) or `{ id, label?, from?, to?, ranges? }`.\n */\nfunction parseEngine(e: unknown): VehicleEngine | null {\n if (typeof e === 'string') return isText(e) ? { id: e, label: labelFromId(e) } : null\n if (Array.isArray(e)) {\n const [id, ...years] = e as unknown[]\n if (!isText(id)) return null\n const ranges: [number, number][] = []\n for (let i = 0; i + 1 < years.length; i += 2) {\n const a = years[i]\n const b = years[i + 1]\n if (isNum(a) && isNum(b)) ranges.push([a, b])\n }\n const first = ranges[0]\n const last = ranges[ranges.length - 1]\n return first && last ? { id, label: labelFromId(id), from: first[0], to: last[1], ranges } : { id, label: labelFromId(id) }\n }\n const o = e as Partial<VehicleEngine> | null\n if (!o || !isText(o.id)) return null\n const ranges = (Array.isArray(o.ranges) ? o.ranges : []).filter((r): r is [number, number] => Array.isArray(r) && isNum(r[0]) && isNum(r[1]))\n return {\n id: o.id,\n label: isText(o.label) ? o.label : labelFromId(o.id),\n ...(isNum(o.from) ? { from: o.from } : {}),\n ...(isNum(o.to) ? { to: o.to } : {}),\n ...(ranges.length ? { ranges } : {}),\n }\n}\n\n/**\n * Reads the vehicle catalog from the props of a content section; drops whatever has the wrong shape.\n * Accepts the compact format (a real catalog weighs tens of KB and OPS caps each write): a model may\n * be a tuple `[id, from, to, engines]`, omit `label` (derived from the id) and list engines as ids.\n * Props stored as text (`makes` as a JSON string) are parsed.\n */\nexport function parseCatalog(raw: unknown, opts: ParseCatalogOptions = {}): VehicleCatalog {\n let makesRaw = (raw as { makes?: unknown } | null | undefined)?.makes\n if (typeof makesRaw === 'string') {\n try {\n makesRaw = JSON.parse(makesRaw)\n } catch {\n makesRaw = []\n }\n }\n const aliases = opts.priorityAliases ?? {}\n const makes: VehicleMake[] = []\n for (const m of Array.isArray(makesRaw) ? makesRaw : []) {\n if (!m || !isText(m.id) || !isText(m.label) || !Array.isArray(m.models)) continue\n const models: VehicleModel[] = []\n for (const entry of m.models) {\n const mo = Array.isArray(entry) ? { id: entry[0], from: entry[1], to: entry[2], engines: entry[3] } : entry\n if (!mo || !isText(mo.id) || !isNum(mo.from) || !isNum(mo.to) || !Array.isArray(mo.engines)) continue\n const engines: VehicleEngine[] = mo.engines.map(parseEngine).filter((e: VehicleEngine | null): e is VehicleEngine => e !== null)\n if (engines.length) models.push({ id: mo.id, label: isText(mo.label) ? mo.label : labelFromId(mo.id), from: mo.from, to: mo.to, engines })\n }\n // A make declared without models (waiting for the import) is kept; one with only invalid models is dropped.\n if (models.length || m.models.length === 0) makes.push({ id: m.id, label: m.label, models, ...makeMeta(m, aliases) })\n }\n return { makes }\n}\n\nexport function vehicleKey(v: { make: string; model: string; year: number | string; engine: string }): string {\n return `${v.make}/${v.model}/${v.year}/${v.engine}`\n}\n\n/** Tag that marks a part as fitting the vehicle with this key. */\nexport function fitTag(key: string, config: FitmentConfig = DEFAULT_FITMENT_CONFIG): string {\n return `${config.tagPrefix}${key}`\n}\n\nconst engineCovers = (e: VehicleEngine, model: VehicleModel, year: number) =>\n e.ranges ? e.ranges.some(([a, b]) => year >= a && year <= b) : year >= (e.from ?? model.from) && year <= (e.to ?? model.to)\n\n// A model covers a year only when one of its engines does: no phantom years inside `from`–`to` that no engine has parts for.\nconst modelCovers = (m: VehicleModel, year: number | null) => year == null || m.engines.some((e) => engineCovers(e, m, year))\n\nfunction num(v: string): number | null {\n const n = Number(v)\n return v !== '' && Number.isInteger(n) ? n : null\n}\n\n/** Resolves a `make/model/year/engine` key against the catalog; null when it does not exist. */\nexport function resolveVehicle(catalog: VehicleCatalog, key: string | null | undefined): Vehicle | null {\n if (!key) return null\n const [makeId, modelId, yearText, engineId] = key.split('/')\n const year = num(yearText ?? '')\n const make = catalog.makes.find((m) => m.id === makeId)\n const model = make?.models.find((m) => m.id === modelId)\n if (!make || !model || year == null || !modelCovers(model, year)) return null\n const engine = model.engines.find((e) => e.id === engineId && engineCovers(e, model, year))\n if (!engine) return null\n return {\n key: vehicleKey({ make: make.id, model: model.id, year, engine: engine.id }),\n make: { id: make.id, label: make.label },\n model: { id: model.id, label: model.label },\n year,\n engine: { id: engine.id, label: engine.label },\n }\n}\n\n/** Short name for chips and badges: \"Make Model 2018\". */\nexport function vehicleName(v: Vehicle): string {\n return `${v.make.label} ${v.model.label} ${v.year}`\n}\n\n/** With engine: \"Make Model 2018 · 1.6 L\". */\nexport function vehicleLongName(v: Vehicle): string {\n return `${vehicleName(v)} · ${v.engine.label}`\n}\n\n// --- Cascade make → year → model → engine ------------------------------------------------------\n// Every list filters by what is already chosen, in any order. With nothing chosen they show\n// everything (so the form also works without JavaScript).\n\nfunction modelsOf(catalog: VehicleCatalog, draft: Pick<VehicleDraft, 'make' | 'year'>): { make: VehicleMake; model: VehicleModel }[] {\n const year = num(draft.year)\n return catalog.makes\n .filter((m) => !draft.make || m.id === draft.make)\n .flatMap((make) => make.models.filter((m) => modelCovers(m, year)).map((model) => ({ make, model })))\n}\n\nexport function makeOptions(catalog: VehicleCatalog): Option[] {\n return catalog.makes.filter((m) => m.models.length > 0).map((m) => ({ value: m.id, label: m.label }))\n}\n\nexport function yearOptions(catalog: VehicleCatalog, draft: Pick<VehicleDraft, 'make' | 'model'>): Option[] {\n const models = catalog.makes\n .filter((m) => !draft.make || m.id === draft.make)\n .flatMap((m) => m.models)\n .filter((m) => !draft.model || m.id === draft.model)\n if (models.length === 0) return []\n const from = Math.min(...models.map((m) => m.from))\n const to = Math.max(...models.map((m) => m.to))\n const years: Option[] = []\n // Only years in which some chosen model has an engine with parts.\n for (let y = to; y >= from; y--) if (models.some((m) => modelCovers(m, y))) years.push({ value: String(y), label: String(y) })\n return years\n}\n\n/**\n * Model options. Model ids repeat across makes (`pick-up` exists in several): with no make chosen\n * the value is `make/model` (unique) and `splitModelValue` sets the make when it is picked; with a\n * make chosen the value is the model id.\n */\nexport function modelOptions(catalog: VehicleCatalog, draft: Pick<VehicleDraft, 'make' | 'year'>): Option[] {\n return modelsOf(catalog, draft).map(({ make, model }) => ({\n value: draft.make ? model.id : `${make.id}/${model.id}`,\n label: draft.make ? model.label : `${make.label} ${model.label}`,\n }))\n}\n\n/** `make/model` (value of the make-less list) → { make, model }; a bare id returns only the model. */\nexport function splitModelValue(value: string): { make?: string; model: string } {\n const i = value.indexOf('/')\n return i === -1 ? { model: value } : { make: value.slice(0, i), model: value.slice(i + 1) }\n}\n\nexport function engineOptions(catalog: VehicleCatalog, draft: Pick<VehicleDraft, 'make' | 'year' | 'model'>): Option[] {\n const year = num(draft.year)\n const seen = new Map<string, string>()\n for (const { model } of modelsOf(catalog, draft).filter(({ model }) => !draft.model || model.id === draft.model)) {\n for (const e of model.engines) {\n if (year == null || engineCovers(e, model, year)) seen.set(e.id, e.label)\n }\n }\n return [...seen].map(([value, label]) => ({ value, label }))\n}\n\n/** Drops what was chosen and is no longer valid after changing another field. */\nexport function reconcileDraft(catalog: VehicleCatalog, draft: VehicleDraft): VehicleDraft {\n let next = { ...draft }\n if (next.year && !yearOptions(catalog, { make: next.make, model: '' }).some((o) => o.value === next.year)) next = { ...next, year: '' }\n // A bare model id sets the make only when it belongs to a single one; repeated ids are ambiguous and dropped.\n if (next.model && !next.make) {\n const owners = catalog.makes.filter((m) => m.models.some((mo) => mo.id === next.model))\n const [owner] = owners\n if (owners.length === 1 && owner) next = { ...next, make: owner.id }\n }\n if (next.model && !modelOptions(catalog, next).some((o) => o.value === next.model)) next = { ...next, model: '' }\n if (next.engine && !engineOptions(catalog, next).some((o) => o.value === next.engine)) next = { ...next, engine: '' }\n return next\n}\n\nexport function draftFromVehicle(v: Vehicle | null): VehicleDraft {\n return v ? { make: v.make.id, year: String(v.year), model: v.model.id, engine: v.engine.id } : EMPTY_DRAFT\n}\n\n/** Complete vehicle from the form, validated against the catalog. */\nexport function vehicleFromDraft(catalog: VehicleCatalog, draft: VehicleDraft): Vehicle | null {\n return resolveVehicle(catalog, vehicleKey({ make: draft.make, model: draft.model, year: draft.year, engine: draft.engine }))\n}\n\n// --- Popular vehicles --------------------------------------------------------------------------\n// They come from the data: makes with `high` priority in the catalog, most references first.\n\nexport function popularMakes(catalog: VehicleCatalog): VehicleMake[] {\n return catalog.makes.filter((m) => m.priority === 'high' && m.models.length > 0).sort((a, b) => (b.refs ?? 0) - (a.refs ?? 0))\n}\n\n/**\n * One shortcut per popular make: its first model in a typical year (the middle of the make's typical\n * range, clamped to the model's range) with the first engine that covers that year.\n */\nexport function popularVehicles(catalog: VehicleCatalog, limit = 6): { key: string; label: string }[] {\n const out: { key: string; label: string }[] = []\n for (const make of popularMakes(catalog)) {\n if (out.length >= limit) break\n const model = make.models[0]\n if (!model) continue\n const mid = make.typical_from != null && make.typical_to != null ? Math.round((make.typical_from + make.typical_to) / 2) : model.to\n const year = Math.min(model.to, Math.max(model.from, mid))\n const engine = model.engines.find((e) => engineCovers(e, model, year))\n if (!engine) continue\n out.push({ key: vehicleKey({ make: make.id, model: model.id, year, engine: engine.id }), label: `${make.label} ${model.label} ${year}` })\n }\n return out\n}\n\n// --- Fitment -----------------------------------------------------------------------------------\n\ntype WithTags = { tags: readonly string[] }\ntype WithAttributes = { attributes: Record<string, unknown> }\n\n/**\n * Does the part fit the vehicle? Only compares what the backend published: a `<prefix><key>` tag\n * (fits), the universal tag (fits everything) or, when the part has other fit tags, no fit. With no\n * fitment data nothing is asserted (\"unknown\").\n */\nexport function fitStatus(product: WithTags, vehicle: { key: string } | null, config: FitmentConfig = DEFAULT_FITMENT_CONFIG): Fit {\n const fits = product.tags.filter((t) => t.startsWith(config.tagPrefix))\n if (fits.length === 0) return 'unknown'\n if (fits.includes(config.universalTag)) return 'universal'\n if (!vehicle) return 'unknown'\n return fits.includes(fitTag(vehicle.key, config)) ? 'fits' : 'no'\n}\n\n/**\n * Values for `products.list({ tags_any })` that list only what fits (own tag plus the universal one; the API takes them\n * as \"any of\"). It replaces searching the tag with `q`. Same as `fitTagsAny` of `@asteby/storefront` for a catalog-driven kind.\n */\nexport function fitFilterTags(vehicle: { key: string }, config: FitmentConfig = DEFAULT_FITMENT_CONFIG): string[] {\n return [fitTag(vehicle.key, config), config.universalTag]\n}\n\nexport type CompatRow = { make: string; model: string; years: string; engine: string; note: string | null }\n\n/** Compatibility table: the attribute holds \"Make|Model|Years|Engine|Note;Make|…\". */\nexport function compatRows(product: WithAttributes, config: FitmentConfig = DEFAULT_FITMENT_CONFIG): CompatRow[] {\n const raw = product.attributes[config.compatAttribute]\n if (typeof raw !== 'string' || !raw) return []\n return raw\n .split(config.rowSeparator)\n .map((row) => row.split(config.cellSeparator).map((c) => c.trim()))\n .flatMap((c): CompatRow[] => {\n const [make, model, years, engine, note] = c\n return c.length >= 4 && make && model ? [{ make, model, years: years ?? '', engine: engine ?? '', note: note || null }] : []\n })\n}\n\n/** \"2012-2019\" contains the year (a bare \"2016\" too). */\nexport function rowCoversYear(years: string, year: number): boolean {\n const [from, to] = years.split(/[–-]/).map((s) => Number(s.trim()))\n if (from === undefined || !Number.isFinite(from)) return false\n return year >= from && year <= (to !== undefined && Number.isFinite(to) ? to : from)\n}\n\n/**\n * The backend publishes one tag per compatible year and engine (hundreds per part). Loaders replace\n * them with ONE `<stateTagPrefix><state>` tag, resolved for the chosen vehicle, so they are not\n * serialized into the page.\n */\nexport function withFit<T extends WithTags>(product: T, vehicle: { key: string } | null, config: FitmentConfig = DEFAULT_FITMENT_CONFIG): T {\n const state = fitStatus(product, vehicle, config)\n return { ...product, tags: [...product.tags.filter((t) => !t.startsWith(config.tagPrefix)), `${config.stateTagPrefix}${state}`] }\n}\n\n/** Fit state already resolved by `withFit` (\"unknown\" if the product did not go through it). */\nexport function fitState(product: WithTags, config: FitmentConfig = DEFAULT_FITMENT_CONFIG): Fit {\n const tag = product.tags.find((t) => t.startsWith(config.stateTagPrefix))\n const value = tag?.slice(config.stateTagPrefix.length)\n return value === 'fits' || value === 'universal' || value === 'no' ? value : 'unknown'\n}\n\n/** Validates a redirect target: only relative paths of the own site. */\nexport function safeRedirect(target: string | null | undefined, fallback = '/'): string {\n return typeof target === 'string' && target.startsWith('/') && !target.startsWith('//') && !target.includes('\\\\') ? target : fallback\n}\n\n/** Removes the selector's error flag (default `?vehicle=invalid`) from a path so it can be returned to clean. */\nexport function withoutQueryParam(path: string, param: string): string {\n const url = new URL(path, 'http://local.invalid')\n url.searchParams.delete(param)\n return `${url.pathname}${url.search}`\n}\n\n// --- Bridge to the buyer's «My items» (contract 1.9) -------------------------------------------------------------\n\n/**\n * The same make → model → engine catalog as the tree the organization publishes for a kind (`subject_catalog` page,\n * `SubjectKind.catalog`): a parts store that already keeps its catalog in this shape converts it once, with a script,\n * and the server validates the buyer's saved vehicles against it (years by `from`/`to`/`ranges`). A model covers the\n * hull of its engines' years, so the tree accepts exactly the years `resolveVehicle` accepts. Makes without models are\n * left out (they do not show in the selector either).\n */\nexport function vehicleCatalogToTree(catalog: VehicleCatalog): SubjectCatalog['tree'] {\n const tree: SubjectCatalog['tree'] = {}\n for (const make of catalog.makes) {\n if (make.models.length === 0) continue\n const models: Record<string, SubjectCatalogNode> = {}\n for (const model of make.models) {\n const engines: Record<string, SubjectCatalogNode> = {}\n let from = Infinity\n let to = -Infinity\n for (const e of model.engines) {\n const spans = e.ranges?.length ? e.ranges : ([[e.from ?? model.from, e.to ?? model.to]] as [number, number][])\n for (const [a, b] of spans) {\n from = Math.min(from, a)\n to = Math.max(to, b)\n }\n engines[e.id] = e.ranges?.length ? { label: e.label, ranges: e.ranges } : { label: e.label, from: e.from ?? model.from, to: e.to ?? model.to }\n }\n models[model.id] = { label: model.label, from: Number.isFinite(from) ? from : model.from, to: Number.isFinite(to) ? to : model.to, children: engines }\n }\n tree[make.id] = { label: make.label, children: models }\n }\n return tree\n}\n"]}
|
package/dist/fitment.d.cts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
+
import { SubjectCatalog } from '@asteby/storefront';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `ranges` are the stretches of model years in which the engine has parts ([from, to] pairs; a gap is a real gap).
|
|
5
|
+
* Without them the engine covers `from`–`to`, or the whole range of its model.
|
|
6
|
+
*/
|
|
1
7
|
type VehicleEngine = {
|
|
2
8
|
id: string;
|
|
3
9
|
label: string;
|
|
4
10
|
from?: number;
|
|
5
11
|
to?: number;
|
|
12
|
+
ranges?: [number, number][];
|
|
6
13
|
};
|
|
7
14
|
type VehicleModel = {
|
|
8
15
|
id: string;
|
|
@@ -146,7 +153,10 @@ type WithAttributes = {
|
|
|
146
153
|
declare function fitStatus(product: WithTags, vehicle: {
|
|
147
154
|
key: string;
|
|
148
155
|
} | null, config?: FitmentConfig): Fit;
|
|
149
|
-
/**
|
|
156
|
+
/**
|
|
157
|
+
* Values for `products.list({ tags_any })` that list only what fits (own tag plus the universal one; the API takes them
|
|
158
|
+
* as "any of"). It replaces searching the tag with `q`. Same as `fitTagsAny` of `@asteby/storefront` for a catalog-driven kind.
|
|
159
|
+
*/
|
|
150
160
|
declare function fitFilterTags(vehicle: {
|
|
151
161
|
key: string;
|
|
152
162
|
}, config?: FitmentConfig): string[];
|
|
@@ -175,5 +185,13 @@ declare function fitState(product: WithTags, config?: FitmentConfig): Fit;
|
|
|
175
185
|
declare function safeRedirect(target: string | null | undefined, fallback?: string): string;
|
|
176
186
|
/** Removes the selector's error flag (default `?vehicle=invalid`) from a path so it can be returned to clean. */
|
|
177
187
|
declare function withoutQueryParam(path: string, param: string): string;
|
|
188
|
+
/**
|
|
189
|
+
* The same make → model → engine catalog as the tree the organization publishes for a kind (`subject_catalog` page,
|
|
190
|
+
* `SubjectKind.catalog`): a parts store that already keeps its catalog in this shape converts it once, with a script,
|
|
191
|
+
* and the server validates the buyer's saved vehicles against it (years by `from`/`to`/`ranges`). A model covers the
|
|
192
|
+
* hull of its engines' years, so the tree accepts exactly the years `resolveVehicle` accepts. Makes without models are
|
|
193
|
+
* left out (they do not show in the selector either).
|
|
194
|
+
*/
|
|
195
|
+
declare function vehicleCatalogToTree(catalog: VehicleCatalog): SubjectCatalog['tree'];
|
|
178
196
|
|
|
179
|
-
export { type CompatRow, DEFAULT_FITMENT_CONFIG, EMPTY_DRAFT, type Fit, type FitmentConfig, type MakePriority, type Option, type ParseCatalogOptions, type Vehicle, type VehicleCatalog, type VehicleDraft, type VehicleEngine, type VehicleMake, type VehicleModel, compatRows, draftFromVehicle, engineOptions, fitFilterTags, fitState, fitStatus, fitTag, fitmentConfig, makeOptions, modelOptions, parseCatalog, popularMakes, popularVehicles, reconcileDraft, resolveVehicle, rowCoversYear, safeRedirect, splitModelValue, vehicleFromDraft, vehicleKey, vehicleLongName, vehicleName, withFit, withoutQueryParam, yearOptions };
|
|
197
|
+
export { type CompatRow, DEFAULT_FITMENT_CONFIG, EMPTY_DRAFT, type Fit, type FitmentConfig, type MakePriority, type Option, type ParseCatalogOptions, type Vehicle, type VehicleCatalog, type VehicleDraft, type VehicleEngine, type VehicleMake, type VehicleModel, compatRows, draftFromVehicle, engineOptions, fitFilterTags, fitState, fitStatus, fitTag, fitmentConfig, makeOptions, modelOptions, parseCatalog, popularMakes, popularVehicles, reconcileDraft, resolveVehicle, rowCoversYear, safeRedirect, splitModelValue, vehicleCatalogToTree, vehicleFromDraft, vehicleKey, vehicleLongName, vehicleName, withFit, withoutQueryParam, yearOptions };
|
package/dist/fitment.d.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
+
import { SubjectCatalog } from '@asteby/storefront';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `ranges` are the stretches of model years in which the engine has parts ([from, to] pairs; a gap is a real gap).
|
|
5
|
+
* Without them the engine covers `from`–`to`, or the whole range of its model.
|
|
6
|
+
*/
|
|
1
7
|
type VehicleEngine = {
|
|
2
8
|
id: string;
|
|
3
9
|
label: string;
|
|
4
10
|
from?: number;
|
|
5
11
|
to?: number;
|
|
12
|
+
ranges?: [number, number][];
|
|
6
13
|
};
|
|
7
14
|
type VehicleModel = {
|
|
8
15
|
id: string;
|
|
@@ -146,7 +153,10 @@ type WithAttributes = {
|
|
|
146
153
|
declare function fitStatus(product: WithTags, vehicle: {
|
|
147
154
|
key: string;
|
|
148
155
|
} | null, config?: FitmentConfig): Fit;
|
|
149
|
-
/**
|
|
156
|
+
/**
|
|
157
|
+
* Values for `products.list({ tags_any })` that list only what fits (own tag plus the universal one; the API takes them
|
|
158
|
+
* as "any of"). It replaces searching the tag with `q`. Same as `fitTagsAny` of `@asteby/storefront` for a catalog-driven kind.
|
|
159
|
+
*/
|
|
150
160
|
declare function fitFilterTags(vehicle: {
|
|
151
161
|
key: string;
|
|
152
162
|
}, config?: FitmentConfig): string[];
|
|
@@ -175,5 +185,13 @@ declare function fitState(product: WithTags, config?: FitmentConfig): Fit;
|
|
|
175
185
|
declare function safeRedirect(target: string | null | undefined, fallback?: string): string;
|
|
176
186
|
/** Removes the selector's error flag (default `?vehicle=invalid`) from a path so it can be returned to clean. */
|
|
177
187
|
declare function withoutQueryParam(path: string, param: string): string;
|
|
188
|
+
/**
|
|
189
|
+
* The same make → model → engine catalog as the tree the organization publishes for a kind (`subject_catalog` page,
|
|
190
|
+
* `SubjectKind.catalog`): a parts store that already keeps its catalog in this shape converts it once, with a script,
|
|
191
|
+
* and the server validates the buyer's saved vehicles against it (years by `from`/`to`/`ranges`). A model covers the
|
|
192
|
+
* hull of its engines' years, so the tree accepts exactly the years `resolveVehicle` accepts. Makes without models are
|
|
193
|
+
* left out (they do not show in the selector either).
|
|
194
|
+
*/
|
|
195
|
+
declare function vehicleCatalogToTree(catalog: VehicleCatalog): SubjectCatalog['tree'];
|
|
178
196
|
|
|
179
|
-
export { type CompatRow, DEFAULT_FITMENT_CONFIG, EMPTY_DRAFT, type Fit, type FitmentConfig, type MakePriority, type Option, type ParseCatalogOptions, type Vehicle, type VehicleCatalog, type VehicleDraft, type VehicleEngine, type VehicleMake, type VehicleModel, compatRows, draftFromVehicle, engineOptions, fitFilterTags, fitState, fitStatus, fitTag, fitmentConfig, makeOptions, modelOptions, parseCatalog, popularMakes, popularVehicles, reconcileDraft, resolveVehicle, rowCoversYear, safeRedirect, splitModelValue, vehicleFromDraft, vehicleKey, vehicleLongName, vehicleName, withFit, withoutQueryParam, yearOptions };
|
|
197
|
+
export { type CompatRow, DEFAULT_FITMENT_CONFIG, EMPTY_DRAFT, type Fit, type FitmentConfig, type MakePriority, type Option, type ParseCatalogOptions, type Vehicle, type VehicleCatalog, type VehicleDraft, type VehicleEngine, type VehicleMake, type VehicleModel, compatRows, draftFromVehicle, engineOptions, fitFilterTags, fitState, fitStatus, fitTag, fitmentConfig, makeOptions, modelOptions, parseCatalog, popularMakes, popularVehicles, reconcileDraft, resolveVehicle, rowCoversYear, safeRedirect, splitModelValue, vehicleCatalogToTree, vehicleFromDraft, vehicleKey, vehicleLongName, vehicleName, withFit, withoutQueryParam, yearOptions };
|
package/dist/fitment.js
CHANGED
|
@@ -33,6 +33,32 @@ function makeMeta(m, aliases) {
|
|
|
33
33
|
function labelFromId(id) {
|
|
34
34
|
return id.split("-").filter(Boolean).join(" ").toUpperCase();
|
|
35
35
|
}
|
|
36
|
+
function parseEngine(e) {
|
|
37
|
+
if (typeof e === "string") return isText(e) ? { id: e, label: labelFromId(e) } : null;
|
|
38
|
+
if (Array.isArray(e)) {
|
|
39
|
+
const [id, ...years] = e;
|
|
40
|
+
if (!isText(id)) return null;
|
|
41
|
+
const ranges2 = [];
|
|
42
|
+
for (let i = 0; i + 1 < years.length; i += 2) {
|
|
43
|
+
const a = years[i];
|
|
44
|
+
const b = years[i + 1];
|
|
45
|
+
if (isNum(a) && isNum(b)) ranges2.push([a, b]);
|
|
46
|
+
}
|
|
47
|
+
const first = ranges2[0];
|
|
48
|
+
const last = ranges2[ranges2.length - 1];
|
|
49
|
+
return first && last ? { id, label: labelFromId(id), from: first[0], to: last[1], ranges: ranges2 } : { id, label: labelFromId(id) };
|
|
50
|
+
}
|
|
51
|
+
const o = e;
|
|
52
|
+
if (!o || !isText(o.id)) return null;
|
|
53
|
+
const ranges = (Array.isArray(o.ranges) ? o.ranges : []).filter((r) => Array.isArray(r) && isNum(r[0]) && isNum(r[1]));
|
|
54
|
+
return {
|
|
55
|
+
id: o.id,
|
|
56
|
+
label: isText(o.label) ? o.label : labelFromId(o.id),
|
|
57
|
+
...isNum(o.from) ? { from: o.from } : {},
|
|
58
|
+
...isNum(o.to) ? { to: o.to } : {},
|
|
59
|
+
...ranges.length ? { ranges } : {}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
36
62
|
function parseCatalog(raw, opts = {}) {
|
|
37
63
|
let makesRaw = raw?.makes;
|
|
38
64
|
if (typeof makesRaw === "string") {
|
|
@@ -50,12 +76,7 @@ function parseCatalog(raw, opts = {}) {
|
|
|
50
76
|
for (const entry of m.models) {
|
|
51
77
|
const mo = Array.isArray(entry) ? { id: entry[0], from: entry[1], to: entry[2], engines: entry[3] } : entry;
|
|
52
78
|
if (!mo || !isText(mo.id) || !isNum(mo.from) || !isNum(mo.to) || !Array.isArray(mo.engines)) continue;
|
|
53
|
-
const engines = mo.engines.map(
|
|
54
|
-
id: e.id,
|
|
55
|
-
label: isText(e.label) ? e.label : labelFromId(e.id),
|
|
56
|
-
...isNum(e.from) ? { from: e.from } : {},
|
|
57
|
-
...isNum(e.to) ? { to: e.to } : {}
|
|
58
|
-
}));
|
|
79
|
+
const engines = mo.engines.map(parseEngine).filter((e) => e !== null);
|
|
59
80
|
if (engines.length) models.push({ id: mo.id, label: isText(mo.label) ? mo.label : labelFromId(mo.id), from: mo.from, to: mo.to, engines });
|
|
60
81
|
}
|
|
61
82
|
if (models.length || m.models.length === 0) makes.push({ id: m.id, label: m.label, models, ...makeMeta(m, aliases) });
|
|
@@ -68,8 +89,8 @@ function vehicleKey(v) {
|
|
|
68
89
|
function fitTag(key, config = DEFAULT_FITMENT_CONFIG) {
|
|
69
90
|
return `${config.tagPrefix}${key}`;
|
|
70
91
|
}
|
|
71
|
-
var engineCovers = (e, model, year) => year >= (e.from ?? model.from) && year <= (e.to ?? model.to);
|
|
72
|
-
var modelCovers = (m, year) => year == null ||
|
|
92
|
+
var engineCovers = (e, model, year) => e.ranges ? e.ranges.some(([a, b]) => year >= a && year <= b) : year >= (e.from ?? model.from) && year <= (e.to ?? model.to);
|
|
93
|
+
var modelCovers = (m, year) => year == null || m.engines.some((e) => engineCovers(e, m, year));
|
|
73
94
|
function num(v) {
|
|
74
95
|
const n = Number(v);
|
|
75
96
|
return v !== "" && Number.isInteger(n) ? n : null;
|
|
@@ -110,7 +131,7 @@ function yearOptions(catalog, draft) {
|
|
|
110
131
|
const from = Math.min(...models.map((m) => m.from));
|
|
111
132
|
const to = Math.max(...models.map((m) => m.to));
|
|
112
133
|
const years = [];
|
|
113
|
-
for (let y = to; y >= from; y--) years.push({ value: String(y), label: String(y) });
|
|
134
|
+
for (let y = to; y >= from; y--) if (models.some((m) => modelCovers(m, y))) years.push({ value: String(y), label: String(y) });
|
|
114
135
|
return years;
|
|
115
136
|
}
|
|
116
137
|
function modelOptions(catalog, draft) {
|
|
@@ -208,7 +229,30 @@ function withoutQueryParam(path, param) {
|
|
|
208
229
|
url.searchParams.delete(param);
|
|
209
230
|
return `${url.pathname}${url.search}`;
|
|
210
231
|
}
|
|
232
|
+
function vehicleCatalogToTree(catalog) {
|
|
233
|
+
const tree = {};
|
|
234
|
+
for (const make of catalog.makes) {
|
|
235
|
+
if (make.models.length === 0) continue;
|
|
236
|
+
const models = {};
|
|
237
|
+
for (const model of make.models) {
|
|
238
|
+
const engines = {};
|
|
239
|
+
let from = Infinity;
|
|
240
|
+
let to = -Infinity;
|
|
241
|
+
for (const e of model.engines) {
|
|
242
|
+
const spans = e.ranges?.length ? e.ranges : [[e.from ?? model.from, e.to ?? model.to]];
|
|
243
|
+
for (const [a, b] of spans) {
|
|
244
|
+
from = Math.min(from, a);
|
|
245
|
+
to = Math.max(to, b);
|
|
246
|
+
}
|
|
247
|
+
engines[e.id] = e.ranges?.length ? { label: e.label, ranges: e.ranges } : { label: e.label, from: e.from ?? model.from, to: e.to ?? model.to };
|
|
248
|
+
}
|
|
249
|
+
models[model.id] = { label: model.label, from: Number.isFinite(from) ? from : model.from, to: Number.isFinite(to) ? to : model.to, children: engines };
|
|
250
|
+
}
|
|
251
|
+
tree[make.id] = { label: make.label, children: models };
|
|
252
|
+
}
|
|
253
|
+
return tree;
|
|
254
|
+
}
|
|
211
255
|
|
|
212
|
-
export { DEFAULT_FITMENT_CONFIG, EMPTY_DRAFT, compatRows, draftFromVehicle, engineOptions, fitFilterTags, fitState, fitStatus, fitTag, fitmentConfig, makeOptions, modelOptions, parseCatalog, popularMakes, popularVehicles, reconcileDraft, resolveVehicle, rowCoversYear, safeRedirect, splitModelValue, vehicleFromDraft, vehicleKey, vehicleLongName, vehicleName, withFit, withoutQueryParam, yearOptions };
|
|
256
|
+
export { DEFAULT_FITMENT_CONFIG, EMPTY_DRAFT, compatRows, draftFromVehicle, engineOptions, fitFilterTags, fitState, fitStatus, fitTag, fitmentConfig, makeOptions, modelOptions, parseCatalog, popularMakes, popularVehicles, reconcileDraft, resolveVehicle, rowCoversYear, safeRedirect, splitModelValue, vehicleCatalogToTree, vehicleFromDraft, vehicleKey, vehicleLongName, vehicleName, withFit, withoutQueryParam, yearOptions };
|
|
213
257
|
//# sourceMappingURL=fitment.js.map
|
|
214
258
|
//# sourceMappingURL=fitment.js.map
|