@salesforce/storefront-next-runtime 0.4.2 → 1.0.0-alpha.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 +9 -3
- package/dist/config.d.ts +33 -221
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +34 -116
- package/dist/config.js.map +1 -1
- package/dist/data-store.d.ts +185 -15
- package/dist/data-store.d.ts.map +1 -1
- package/dist/data-store.js +412 -10
- package/dist/data-store.js.map +1 -1
- package/dist/design-data.d.ts +266 -62
- package/dist/design-data.d.ts.map +1 -1
- package/dist/design-data.js +399 -14
- package/dist/design-data.js.map +1 -1
- package/dist/design-mode.d.ts +3 -2
- package/dist/design-mode.d.ts.map +1 -1
- package/dist/design-react-core.d.ts +2 -2
- package/dist/events.d.ts +32 -6
- package/dist/events.d.ts.map +1 -1
- package/dist/i18n-client.d.ts.map +1 -1
- package/dist/i18n-client.js.map +1 -1
- package/dist/i18n.d.ts +1 -2
- package/dist/i18n.d.ts.map +1 -1
- package/dist/modeDetection.js +0 -18
- package/dist/modeDetection.js.map +1 -1
- package/dist/scapi.d.ts +2185 -466
- package/dist/scapi.d.ts.map +1 -1
- package/dist/scapi.js +1 -1
- package/dist/scapi.js.map +1 -1
- package/dist/schema.d.ts +17 -15
- package/dist/schema.d.ts.map +1 -1
- package/dist/site-context.d.ts +43 -27
- package/dist/site-context.d.ts.map +1 -1
- package/dist/site-context.js +2 -2
- package/dist/site-context2.js +41 -31
- package/dist/site-context2.js.map +1 -1
- package/dist/types.d.ts +19 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types2.d.ts +89 -63
- package/dist/types2.d.ts.map +1 -1
- package/package.json +1 -19
- package/dist/custom-global-preferences.d.ts +0 -20
- package/dist/custom-global-preferences.d.ts.map +0 -1
- package/dist/custom-global-preferences.js +0 -31
- package/dist/custom-global-preferences.js.map +0 -1
- package/dist/custom-site-preferences.d.ts +0 -20
- package/dist/custom-site-preferences.d.ts.map +0 -1
- package/dist/custom-site-preferences.js +0 -31
- package/dist/custom-site-preferences.js.map +0 -1
- package/dist/data-store-custom-global-preferences.d.ts +0 -2
- package/dist/data-store-custom-global-preferences.js +0 -6
- package/dist/data-store-custom-site-preferences.d.ts +0 -2
- package/dist/data-store-custom-site-preferences.js +0 -6
- package/dist/data-store-gcp-preferences.d.ts +0 -2
- package/dist/data-store-gcp-preferences.js +0 -6
- package/dist/gcp-preferences.d.ts +0 -52
- package/dist/gcp-preferences.d.ts.map +0 -1
- package/dist/gcp-preferences.js +0 -64
- package/dist/gcp-preferences.js.map +0 -1
- package/dist/utils.js +0 -90
- package/dist/utils.js.map +0 -1
package/dist/design-data.js
CHANGED
|
@@ -349,6 +349,24 @@ function transformRegion(region, visitor) {
|
|
|
349
349
|
*/
|
|
350
350
|
const BARE_EXPRESSION_PATTERN = /^(\w+)\.(\w+)$/;
|
|
351
351
|
/**
|
|
352
|
+
* Coerces a string value returned by the data binding API into a boolean or
|
|
353
|
+
* number when the contents represent one. The data provider returns every
|
|
354
|
+
* field as a string, so callers expecting typed values would otherwise receive
|
|
355
|
+
* `"true"` instead of `true` or `"2026"` instead of `2026`.
|
|
356
|
+
*
|
|
357
|
+
* Non-string inputs are returned as-is. Strings that are neither booleans nor
|
|
358
|
+
* finite numbers are returned unchanged.
|
|
359
|
+
*/
|
|
360
|
+
function parseFieldValue(value) {
|
|
361
|
+
if (typeof value !== "string") return value;
|
|
362
|
+
if (value === "true") return true;
|
|
363
|
+
if (value === "false") return false;
|
|
364
|
+
if (value.trim() === "") return value;
|
|
365
|
+
const num = Number(value);
|
|
366
|
+
if (Number.isFinite(num)) return num;
|
|
367
|
+
return value;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
352
370
|
* Parses a binding expression string into its provider type and field name.
|
|
353
371
|
* Supports the bare `type.field` format.
|
|
354
372
|
*
|
|
@@ -389,7 +407,7 @@ function resolveExpression(expression, contexts, dataBindings) {
|
|
|
389
407
|
if (!context) return "";
|
|
390
408
|
const record = dataBindings[context.type]?.[context.id];
|
|
391
409
|
if (!record) return "";
|
|
392
|
-
return record[parsed.field] ?? "";
|
|
410
|
+
return parseFieldValue(record[parsed.field] ?? "");
|
|
393
411
|
}
|
|
394
412
|
/**
|
|
395
413
|
* Resolves data binding expressions for a single component. Replaces attribute
|
|
@@ -452,6 +470,286 @@ function resolveComponentDataBindings(component, binding, dataBindings) {
|
|
|
452
470
|
};
|
|
453
471
|
}
|
|
454
472
|
|
|
473
|
+
//#endregion
|
|
474
|
+
//#region src/design/data/page/markup-url-rewriter.ts
|
|
475
|
+
const STATICLINK_PATTERN = /\?\$staticlink\$/gi;
|
|
476
|
+
const STATICLINK_DELIMITERS_SINGLE = "\":='(>";
|
|
477
|
+
const STATICLINK_DELIMITERS_DOUBLE = [
|
|
478
|
+
"\"[",
|
|
479
|
+
"=[",
|
|
480
|
+
",[",
|
|
481
|
+
" [",
|
|
482
|
+
" ,",
|
|
483
|
+
", "
|
|
484
|
+
];
|
|
485
|
+
let warnedStaticlink = false;
|
|
486
|
+
function rewriteImages(source, ctx) {
|
|
487
|
+
const domain = ctx.pageLibraryDomain;
|
|
488
|
+
if (!domain) {
|
|
489
|
+
if (!warnedStaticlink) {
|
|
490
|
+
warnedStaticlink = true;
|
|
491
|
+
ctx.onWarn?.({
|
|
492
|
+
kind: "staticlink-rewrite-skipped",
|
|
493
|
+
message: "?$staticlink$ rewrite skipped: ctx.pageLibraryDomain is not set",
|
|
494
|
+
typeId: "",
|
|
495
|
+
attrId: "",
|
|
496
|
+
attrType: "markup"
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
return source;
|
|
500
|
+
}
|
|
501
|
+
const resolveStaticUrl = ctx.staticLinkFor ?? ctx.resolveMediaUrl;
|
|
502
|
+
let result = "";
|
|
503
|
+
let lastPos = -1;
|
|
504
|
+
STATICLINK_PATTERN.lastIndex = 0;
|
|
505
|
+
let match = STATICLINK_PATTERN.exec(source);
|
|
506
|
+
if (!match) return source;
|
|
507
|
+
while (match) {
|
|
508
|
+
const pos = match.index;
|
|
509
|
+
const newPos = STATICLINK_PATTERN.lastIndex;
|
|
510
|
+
let startPos = pos - 1;
|
|
511
|
+
while (true) {
|
|
512
|
+
if (startPos <= lastPos) break;
|
|
513
|
+
const ch = source.charAt(startPos);
|
|
514
|
+
if (STATICLINK_DELIMITERS_SINGLE.indexOf(ch) !== -1) {
|
|
515
|
+
if (!(ch === "=" && startPos + 1 < source.length && source.charAt(startPos + 1) === ".")) break;
|
|
516
|
+
}
|
|
517
|
+
if (startPos > 0) {
|
|
518
|
+
const doubleChar = source.substring(startPos - 1, startPos + 1);
|
|
519
|
+
if (STATICLINK_DELIMITERS_DOUBLE.includes(doubleChar)) break;
|
|
520
|
+
}
|
|
521
|
+
startPos--;
|
|
522
|
+
}
|
|
523
|
+
const leftStart = lastPos === -1 ? 0 : lastPos;
|
|
524
|
+
result += source.substring(leftStart, startPos + 1);
|
|
525
|
+
const path = source.substring(startPos + 1, pos);
|
|
526
|
+
if (path.trim().length !== 0) {
|
|
527
|
+
let url = resolveStaticUrl({
|
|
528
|
+
libraryDomain: domain,
|
|
529
|
+
path: path.trim(),
|
|
530
|
+
locale: ctx.locale
|
|
531
|
+
});
|
|
532
|
+
if (path.startsWith(" ")) url = ` ${url}`;
|
|
533
|
+
if (path.endsWith(" ")) url += " ";
|
|
534
|
+
result += url;
|
|
535
|
+
}
|
|
536
|
+
lastPos = newPos;
|
|
537
|
+
match = STATICLINK_PATTERN.exec(source);
|
|
538
|
+
}
|
|
539
|
+
const tailStart = lastPos === -1 ? 0 : lastPos;
|
|
540
|
+
result += source.substring(tailStart);
|
|
541
|
+
return result;
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Rewrites `?$staticlink$` placeholders in markup to fully-qualified
|
|
545
|
+
* static-content URLs. Pipeline-action placeholders pass through unchanged.
|
|
546
|
+
*/
|
|
547
|
+
function rewriteMarkup(source, ctx) {
|
|
548
|
+
if (!source) return "";
|
|
549
|
+
return rewriteImages(source, ctx);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
//#endregion
|
|
553
|
+
//#region src/design/data/page/attribute-resolution.ts
|
|
554
|
+
/**
|
|
555
|
+
* Module-scoped dedup set for unknown-type / malformed-envelope warnings.
|
|
556
|
+
* Keyed by `${kind}|${typeId}|${attrId}|${attrType}` so two different
|
|
557
|
+
* issues on the same attribute (e.g. malformed-image then later
|
|
558
|
+
* unknown-type) both fire once.
|
|
559
|
+
*/
|
|
560
|
+
const warnedKeys = /* @__PURE__ */ new Set();
|
|
561
|
+
/**
|
|
562
|
+
* Routes a structured warning to the consumer's `onWarn` handler at most
|
|
563
|
+
* once per `(kind, typeId, attrId, attrType)` triple. When no handler is
|
|
564
|
+
* configured the runtime stays silent — production callers are expected to
|
|
565
|
+
* supply a handler.
|
|
566
|
+
*/
|
|
567
|
+
function warnOnce(ctx, kind, typeId, attrId, attrType, message) {
|
|
568
|
+
if (!ctx.onWarn) return;
|
|
569
|
+
const key = `${kind}|${typeId}|${attrId}|${attrType}`;
|
|
570
|
+
if (warnedKeys.has(key)) return;
|
|
571
|
+
warnedKeys.add(key);
|
|
572
|
+
ctx.onWarn({
|
|
573
|
+
kind,
|
|
574
|
+
message,
|
|
575
|
+
typeId,
|
|
576
|
+
attrId,
|
|
577
|
+
attrType
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Returns true when `value` is shaped like an {@link ImageEnvelope}. Used
|
|
582
|
+
* during structural dispatch (when `componentTypes` is unavailable) to
|
|
583
|
+
* recognize image attributes without `attrDef.type`.
|
|
584
|
+
*/
|
|
585
|
+
function isImageEnvelope(value) {
|
|
586
|
+
if (!value || typeof value !== "object") return false;
|
|
587
|
+
const media = value.media;
|
|
588
|
+
return media != null && typeof media === "object" && typeof media.libraryDomain === "string" && typeof media.path === "string";
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Converts an {@link ImageEnvelope} to the resolved SCAPI shape by stamping
|
|
592
|
+
* the URL. Returns the original value untouched if the envelope is
|
|
593
|
+
* malformed (missing `media.libraryDomain` or `media.path`); a warning is
|
|
594
|
+
* logged once per `(typeId, attrId, attrType)` triple so production logs
|
|
595
|
+
* don't drown.
|
|
596
|
+
*/
|
|
597
|
+
function resolveImageAttribute(value, typeId, attrId, attrType, ctx) {
|
|
598
|
+
if (!isImageEnvelope(value)) {
|
|
599
|
+
warnOnce(ctx, "malformed-image", typeId, attrId, attrType, "malformed image envelope, passing through unchanged");
|
|
600
|
+
return value;
|
|
601
|
+
}
|
|
602
|
+
const out = { url: ctx.resolveMediaUrl({
|
|
603
|
+
libraryDomain: value.media.libraryDomain,
|
|
604
|
+
path: value.media.path,
|
|
605
|
+
locale: ctx.locale
|
|
606
|
+
}) };
|
|
607
|
+
if (value.focalPoint) out.focalPoint = value.focalPoint;
|
|
608
|
+
if (value.metaData) out.metaData = value.metaData;
|
|
609
|
+
return out;
|
|
610
|
+
}
|
|
611
|
+
function isFileEnvelope(value) {
|
|
612
|
+
if (!value || typeof value !== "object") return false;
|
|
613
|
+
const candidate = value;
|
|
614
|
+
const media = candidate.media;
|
|
615
|
+
return media != null && typeof media === "object" && typeof media.libraryDomain === "string" && typeof media.path === "string" && !("focalPoint" in candidate || "metaData" in candidate);
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Resolves a file envelope to a URL string. Matches SCAPI's
|
|
619
|
+
* `mediaFile.getAbsURL().toString()` — file attributes emit a plain URL
|
|
620
|
+
* string, not an object envelope.
|
|
621
|
+
*/
|
|
622
|
+
function resolveFileAttribute(value, typeId, attrId, ctx) {
|
|
623
|
+
if (!isFileEnvelope(value)) {
|
|
624
|
+
warnOnce(ctx, "malformed-file", typeId, attrId, "file", "malformed file envelope, passing through unchanged");
|
|
625
|
+
return value;
|
|
626
|
+
}
|
|
627
|
+
return ctx.resolveMediaUrl({
|
|
628
|
+
libraryDomain: value.media.libraryDomain,
|
|
629
|
+
path: value.media.path,
|
|
630
|
+
locale: ctx.locale
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
const MAX_CMS_RECORD_DEPTH = 10;
|
|
634
|
+
function isCmsRecordEnvelope(value) {
|
|
635
|
+
if (!value || typeof value !== "object") return false;
|
|
636
|
+
const candidate = value;
|
|
637
|
+
if (typeof candidate.id !== "string") return false;
|
|
638
|
+
const type = candidate.type;
|
|
639
|
+
if (!type || typeof type !== "object" || typeof type.id !== "string") return false;
|
|
640
|
+
if (!Array.isArray(type.attributeDefinitions)) return false;
|
|
641
|
+
return candidate.attributes != null && typeof candidate.attributes === "object";
|
|
642
|
+
}
|
|
643
|
+
function resolveCmsRecordAttribute(value, typeId, attrId, ctx, depth) {
|
|
644
|
+
if (value == null) return value;
|
|
645
|
+
if (!isCmsRecordEnvelope(value)) {
|
|
646
|
+
warnOnce(ctx, "malformed-cms-record", typeId, attrId, "cms_record", "malformed cms_record envelope, passing through unchanged");
|
|
647
|
+
return value;
|
|
648
|
+
}
|
|
649
|
+
if (depth >= MAX_CMS_RECORD_DEPTH) {
|
|
650
|
+
warnOnce(ctx, "cms-record-depth-exceeded", typeId, attrId, "cms_record", `cms_record nesting depth exceeded (max ${MAX_CMS_RECORD_DEPTH}), passing through unchanged`);
|
|
651
|
+
return value;
|
|
652
|
+
}
|
|
653
|
+
const innerDefs = value.type.attributeDefinitions;
|
|
654
|
+
const resolvedAttrs = resolveCmsRecordInnerAttributes(value.attributes, typeId, innerDefs, ctx, depth + 1);
|
|
655
|
+
return {
|
|
656
|
+
id: value.id,
|
|
657
|
+
type: value.type,
|
|
658
|
+
attributes: resolvedAttrs
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
function resolveCmsRecordInnerAttributes(data, typeId, defs, ctx, depth) {
|
|
662
|
+
const out = {};
|
|
663
|
+
const defsById = /* @__PURE__ */ new Map();
|
|
664
|
+
for (const def of defs) defsById.set(def.id, def);
|
|
665
|
+
for (const [attrId, value] of Object.entries(data)) {
|
|
666
|
+
const def = defsById.get(attrId);
|
|
667
|
+
if (!def) {
|
|
668
|
+
out[attrId] = value;
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
out[attrId] = dispatchCmsRecordInner(value, typeId, attrId, def, ctx, depth);
|
|
672
|
+
}
|
|
673
|
+
return out;
|
|
674
|
+
}
|
|
675
|
+
function dispatchCmsRecordInner(value, typeId, attrId, attrDef, ctx, depth) {
|
|
676
|
+
if (attrDef.type === "cms_record") return resolveCmsRecordAttribute(value, typeId, attrId, ctx, depth);
|
|
677
|
+
return dispatchByType(value, typeId, attrId, attrDef, ctx);
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Resolves every attribute on a component's `data` map to the wire shape
|
|
681
|
+
* SCAPI `getPage` would have returned.
|
|
682
|
+
*
|
|
683
|
+
* Dispatch is type-driven when {@code typeAttributeDefinitions} is supplied.
|
|
684
|
+
* Otherwise the resolver inspects each value structurally — it recognizes
|
|
685
|
+
* the image envelope by the presence of `media.libraryDomain` and
|
|
686
|
+
* `media.path` and passes everything else through unchanged.
|
|
687
|
+
*
|
|
688
|
+
* Forward-compatibility (Q9): unknown attribute types pass through. Each
|
|
689
|
+
* `(typeId, attrId, attrType)` triple is logged once per process via a
|
|
690
|
+
* module-scoped dedup set.
|
|
691
|
+
*
|
|
692
|
+
* @param data attribute map to resolve, already
|
|
693
|
+
* locale-merged + data-binding-resolved by
|
|
694
|
+
* {@link processPage}.
|
|
695
|
+
* @param typeId component type identifier, used as part
|
|
696
|
+
* of the dedup key for warnings. Empty
|
|
697
|
+
* string is acceptable for anonymous
|
|
698
|
+
* callers (page-level data).
|
|
699
|
+
* @param typeAttributeDefinitions attribute definitions for {@code typeId}
|
|
700
|
+
* from `manifest.componentTypes`. When
|
|
701
|
+
* omitted, falls back to structural
|
|
702
|
+
* detection of the image envelope.
|
|
703
|
+
* @param ctx per-request resolution surface.
|
|
704
|
+
* @returns a new map with each attribute's value replaced by the resolved
|
|
705
|
+
* wire shape; pass-through for any attribute type the resolver
|
|
706
|
+
* doesn't yet recognize.
|
|
707
|
+
*/
|
|
708
|
+
function resolveAttributeValues(data, typeId, typeAttributeDefinitions, ctx) {
|
|
709
|
+
if (!data) return {};
|
|
710
|
+
const out = {};
|
|
711
|
+
if (typeAttributeDefinitions && Object.keys(typeAttributeDefinitions).length > 0) {
|
|
712
|
+
for (const [attrId, value] of Object.entries(data)) {
|
|
713
|
+
const def = typeAttributeDefinitions[attrId];
|
|
714
|
+
if (!def) {
|
|
715
|
+
out[attrId] = value;
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
out[attrId] = dispatchByType(value, typeId, attrId, def, ctx);
|
|
719
|
+
}
|
|
720
|
+
return out;
|
|
721
|
+
}
|
|
722
|
+
for (const [attrId, value] of Object.entries(data)) if (isImageEnvelope(value)) out[attrId] = resolveImageAttribute(value, typeId, attrId, "image", ctx);
|
|
723
|
+
else out[attrId] = value;
|
|
724
|
+
return out;
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Type-driven dispatch. Unknown types fall through with a deduped warning
|
|
728
|
+
* (Q9) — the principle is that a runtime older than ECOM should still
|
|
729
|
+
* produce *something* rather than dropping the value.
|
|
730
|
+
*/
|
|
731
|
+
function dispatchByType(value, typeId, attrId, attrDef, ctx) {
|
|
732
|
+
switch (attrDef.type) {
|
|
733
|
+
case "image": return resolveImageAttribute(value, typeId, attrId, attrDef.type, ctx);
|
|
734
|
+
case "markup": return typeof value === "string" ? rewriteMarkup(value, ctx) : value;
|
|
735
|
+
case "file": return resolveFileAttribute(value, typeId, attrId, ctx);
|
|
736
|
+
case "cms_record": return resolveCmsRecordAttribute(value, typeId, attrId, ctx, 0);
|
|
737
|
+
case "string":
|
|
738
|
+
case "text":
|
|
739
|
+
case "url":
|
|
740
|
+
case "boolean":
|
|
741
|
+
case "integer":
|
|
742
|
+
case "enum":
|
|
743
|
+
case "custom":
|
|
744
|
+
case "product":
|
|
745
|
+
case "category":
|
|
746
|
+
case "page": return value;
|
|
747
|
+
default:
|
|
748
|
+
warnOnce(ctx, "unknown-attribute-type", typeId, attrId, attrDef.type, "unknown attribute type, passing through unchanged");
|
|
749
|
+
return value;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
455
753
|
//#endregion
|
|
456
754
|
//#region src/design/data/validate-rule.ts
|
|
457
755
|
/**
|
|
@@ -498,7 +796,7 @@ function resolveComponentDataBindings(component, binding, dataBindings) {
|
|
|
498
796
|
* ```
|
|
499
797
|
*/
|
|
500
798
|
function validateRule(rule, locale, context) {
|
|
501
|
-
if (rule.campaignQualifiers) {
|
|
799
|
+
if (rule.campaignQualifiers?.length) {
|
|
502
800
|
for (const campaignQualifier of rule.campaignQualifiers) if (!context?.campaignQualifiers?.[campaignQualifier.campaignId]?.[campaignQualifier.promotionId]) return false;
|
|
503
801
|
} else {
|
|
504
802
|
if (rule.activeLocales && !rule.activeLocales.includes(locale)) return false;
|
|
@@ -573,9 +871,49 @@ function validateRule(rule, locale, context) {
|
|
|
573
871
|
* // "loyalty-offer" was removed because the shopper isn't a loyalty member
|
|
574
872
|
* ```
|
|
575
873
|
*/
|
|
874
|
+
/**
|
|
875
|
+
* Builds a component's `data` map by walking each attribute definition and
|
|
876
|
+
* picking the first non-undefined value in priority order:
|
|
877
|
+
*
|
|
878
|
+
* active-locale content → fallback-locale content → attrDef.defaultValue
|
|
879
|
+
*
|
|
880
|
+
* If none of those have a value the attribute is omitted from the result.
|
|
881
|
+
*
|
|
882
|
+
* When no `typeDefs` are supplied, we fall back to the legacy behavior:
|
|
883
|
+
* `{ ...nodeData, ...defaultContent, ...localeContent }`. This keeps
|
|
884
|
+
* already-deployed manifests rendering until the manifest builder starts
|
|
885
|
+
* emitting `componentTypes`.
|
|
886
|
+
*/
|
|
887
|
+
function composeComponentData({ nodeData, defaultContent, localeContent, typeDefs }) {
|
|
888
|
+
if (!typeDefs || Object.keys(typeDefs).length === 0) return {
|
|
889
|
+
...nodeData ?? {},
|
|
890
|
+
...defaultContent,
|
|
891
|
+
...localeContent
|
|
892
|
+
};
|
|
893
|
+
const result = {};
|
|
894
|
+
for (const attrId of Object.keys(typeDefs)) {
|
|
895
|
+
const def = typeDefs[attrId];
|
|
896
|
+
if (Object.prototype.hasOwnProperty.call(localeContent, attrId)) result[attrId] = localeContent[attrId];
|
|
897
|
+
else if (Object.prototype.hasOwnProperty.call(defaultContent, attrId)) result[attrId] = defaultContent[attrId];
|
|
898
|
+
else if (def.defaultValue !== void 0) result[attrId] = def.defaultValue;
|
|
899
|
+
}
|
|
900
|
+
return result;
|
|
901
|
+
}
|
|
576
902
|
function processPage(page, processorContext) {
|
|
577
903
|
const { pruneInvisible = true } = processorContext;
|
|
578
904
|
return transformPage(page, {
|
|
905
|
+
visitPage(ctx) {
|
|
906
|
+
const pageNode = ctx.node;
|
|
907
|
+
const result = {
|
|
908
|
+
...pageNode,
|
|
909
|
+
regions: ctx.visitRegions(pageNode.regions)
|
|
910
|
+
};
|
|
911
|
+
if (pageNode.data !== void 0) {
|
|
912
|
+
const typeDefs = processorContext.componentTypes?.[pageNode.typeId]?.attributeDefinitions;
|
|
913
|
+
result.data = resolveAttributeValues(pageNode.data, pageNode.typeId, typeDefs, processorContext.attrCtx);
|
|
914
|
+
}
|
|
915
|
+
return result;
|
|
916
|
+
},
|
|
579
917
|
visitRegion(ctx) {
|
|
580
918
|
let regionInfo;
|
|
581
919
|
if (ctx.parent?.type === "page") regionInfo = processorContext.pageInfo.regions[ctx.node.id];
|
|
@@ -610,23 +948,32 @@ function processPage(page, processorContext) {
|
|
|
610
948
|
isVisible = false;
|
|
611
949
|
}
|
|
612
950
|
}
|
|
613
|
-
const defaultContent = componentInfo?.content?.
|
|
951
|
+
const defaultContent = componentInfo?.content?.[processorContext.defaultLocale] ?? {};
|
|
614
952
|
const localeContent = componentInfo?.content?.[processorContext.locale] ?? {};
|
|
615
|
-
const content = {
|
|
616
|
-
...defaultContent,
|
|
617
|
-
...localeContent
|
|
618
|
-
};
|
|
619
953
|
const isLocalized = Boolean(componentInfo?.content?.[processorContext.locale]);
|
|
954
|
+
const typeDefs = processorContext.componentTypes?.[ctx.node.typeId]?.attributeDefinitions;
|
|
955
|
+
const composedData = composeComponentData({
|
|
956
|
+
nodeData: ctx.node.data,
|
|
957
|
+
defaultContent,
|
|
958
|
+
localeContent,
|
|
959
|
+
typeDefs
|
|
960
|
+
});
|
|
961
|
+
const name = componentInfo?.name ?? ctx.node.name;
|
|
962
|
+
const fragment = componentInfo?.fragment ?? ctx.node.fragment ?? false;
|
|
620
963
|
let node = {
|
|
621
964
|
...ctx.node,
|
|
965
|
+
name,
|
|
966
|
+
fragment,
|
|
622
967
|
localized: isLocalized,
|
|
623
968
|
visible: isVisible,
|
|
624
|
-
data:
|
|
625
|
-
...ctx.node.data,
|
|
626
|
-
...content
|
|
627
|
-
}
|
|
969
|
+
data: composedData
|
|
628
970
|
};
|
|
629
971
|
node = resolveComponentDataBindings(node, componentInfo?.dataBinding, processorContext.qualifiers?.dataBindings);
|
|
972
|
+
const resolvedData = resolveAttributeValues(node.data, node.typeId, typeDefs, processorContext.attrCtx);
|
|
973
|
+
node = {
|
|
974
|
+
...node,
|
|
975
|
+
data: resolvedData
|
|
976
|
+
};
|
|
630
977
|
return {
|
|
631
978
|
...node,
|
|
632
979
|
regions: ctx.visitRegions(ctx.node.regions)
|
|
@@ -878,6 +1225,36 @@ async function getPageFromManifest(manifest, { contextResolver, locale }) {
|
|
|
878
1225
|
//#endregion
|
|
879
1226
|
//#region src/design/data/page/resolve-page.ts
|
|
880
1227
|
/**
|
|
1228
|
+
* Page metadata fields the manifest builder may locale-overlay. Used by
|
|
1229
|
+
* {@link applyPageMetadataOverlay} to know which keys to copy from the
|
|
1230
|
+
* overlay onto the resolved page; structural fields like `id`, `typeId`,
|
|
1231
|
+
* and `regions` are intentionally excluded.
|
|
1232
|
+
*/
|
|
1233
|
+
const PAGE_METADATA_OVERLAY_KEYS = [
|
|
1234
|
+
"name",
|
|
1235
|
+
"aspectTypeId",
|
|
1236
|
+
"description",
|
|
1237
|
+
"pageTitle",
|
|
1238
|
+
"pageDescription",
|
|
1239
|
+
"pageKeywords"
|
|
1240
|
+
];
|
|
1241
|
+
/**
|
|
1242
|
+
* Applies a per-locale page metadata overlay to the variation's default-locale
|
|
1243
|
+
* page. The overlay is a **full replacement** for the listed metadata fields
|
|
1244
|
+
* — when a key is present in the overlay it wins; when absent we fall through
|
|
1245
|
+
* to the default-locale value (Q6 of the design plan).
|
|
1246
|
+
*
|
|
1247
|
+
* Returns a shallow copy of the page with overlaid fields applied. Structural
|
|
1248
|
+
* fields (`id`, `typeId`, `regions`, `data`) are never touched.
|
|
1249
|
+
*/
|
|
1250
|
+
function applyPageMetadataOverlay(variation, locale) {
|
|
1251
|
+
const overlay = variation.pageContent?.[locale];
|
|
1252
|
+
if (!overlay) return variation.page;
|
|
1253
|
+
const out = { ...variation.page };
|
|
1254
|
+
for (const key of PAGE_METADATA_OVERLAY_KEYS) if (overlay[key] !== void 0) out[key] = overlay[key];
|
|
1255
|
+
return out;
|
|
1256
|
+
}
|
|
1257
|
+
/**
|
|
881
1258
|
* Main entry point for the page resolution pipeline. Orchestrates the full flow:
|
|
882
1259
|
*
|
|
883
1260
|
* 1. **Resolve dynamic page ID** — For product/category identifiers, looks up
|
|
@@ -934,7 +1311,7 @@ async function getPageFromManifest(manifest, { contextResolver, locale }) {
|
|
|
934
1311
|
* }
|
|
935
1312
|
* ```
|
|
936
1313
|
*/
|
|
937
|
-
async function resolvePage({ id, identifierType, aspectType, locale, manifestStorage, contextResolver, pruneInvisible = true }) {
|
|
1314
|
+
async function resolvePage({ id, identifierType, aspectType, locale, defaultLocale, manifestStorage, contextResolver, attrCtx, pruneInvisible = true }) {
|
|
938
1315
|
let resolvedId = null;
|
|
939
1316
|
if (ContentAssignmentResolvers.has(identifierType)) {
|
|
940
1317
|
const siteManifest = await manifestStorage.getSiteManifest();
|
|
@@ -956,15 +1333,23 @@ async function resolvePage({ id, identifierType, aspectType, locale, manifestSto
|
|
|
956
1333
|
if (!pageResults) return null;
|
|
957
1334
|
let context = null;
|
|
958
1335
|
if (pageResults.entry.pageRequiresContext) context = pageResults.context ?? await contextResolver?.(pageManifest.context) ?? null;
|
|
959
|
-
|
|
1336
|
+
const localizedPage = applyPageMetadataOverlay(pageResults.entry, locale);
|
|
1337
|
+
const resolvedAttrCtx = pageManifest.pageLibraryDomain && !attrCtx.pageLibraryDomain ? {
|
|
1338
|
+
...attrCtx,
|
|
1339
|
+
pageLibraryDomain: pageManifest.pageLibraryDomain
|
|
1340
|
+
} : attrCtx;
|
|
1341
|
+
return processPage(localizedPage, {
|
|
960
1342
|
qualifiers: context,
|
|
961
1343
|
componentInfo: pageManifest.componentInfo,
|
|
962
1344
|
pageInfo: { regions: pageResults.entry.regions },
|
|
963
1345
|
locale,
|
|
1346
|
+
defaultLocale,
|
|
1347
|
+
attrCtx: resolvedAttrCtx,
|
|
1348
|
+
componentTypes: pageManifest.componentTypes,
|
|
964
1349
|
pruneInvisible
|
|
965
1350
|
});
|
|
966
1351
|
}
|
|
967
1352
|
|
|
968
1353
|
//#endregion
|
|
969
|
-
export { ContentAssignmentResolvers, RequiredError, getPageFromManifest, parseExpression, processPage, resolveComponentDataBindings, resolveDynamicPageId, resolveExpression, resolvePage, transformComponent, transformPage, transformRegion, validateRule };
|
|
1354
|
+
export { ContentAssignmentResolvers, RequiredError, getPageFromManifest, parseExpression, processPage, resolveAttributeValues, resolveComponentDataBindings, resolveDynamicPageId, resolveExpression, resolvePage, rewriteMarkup, transformComponent, transformPage, transformRegion, validateRule };
|
|
970
1355
|
//# sourceMappingURL=design-data.js.map
|