@vectoriox/iox-ui 4.10.0 → 4.12.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.
|
@@ -2410,6 +2410,48 @@ function resolveRepeaterItems(resolved, sourcePath) {
|
|
|
2410
2410
|
return resolved;
|
|
2411
2411
|
return resolved == null ? [] : [resolved];
|
|
2412
2412
|
}
|
|
2413
|
+
// ── Referenced data sources (shared: builder-save + SSR resolve) ─────────────────────────────────
|
|
2414
|
+
// Which data-source aliases a node tree uses, so a consumer resolves ONLY what a page/global needs
|
|
2415
|
+
// (not the whole org library). Used by cms-client (persist globals' sources) and the SSR engine
|
|
2416
|
+
// (resolve the referenced subset per page). See org-data-sources-plan.md.
|
|
2417
|
+
/** Aliases referenced anywhere in a node tree: a repeater/slider `source` (a `bindings` entry with
|
|
2418
|
+
* `prop === 'source'`, or `props.source` for the slider) plus every binding's `source`. */
|
|
2419
|
+
function collectReferencedAliases(nodes, acc = new Set()) {
|
|
2420
|
+
for (const n of nodes ?? []) {
|
|
2421
|
+
for (const b of n?.bindings ?? []) {
|
|
2422
|
+
if (b?.source)
|
|
2423
|
+
acc.add(b.source);
|
|
2424
|
+
}
|
|
2425
|
+
if (n?.props?.source)
|
|
2426
|
+
acc.add(n.props.source);
|
|
2427
|
+
if (n?.children?.length)
|
|
2428
|
+
collectReferencedAliases(n.children, acc);
|
|
2429
|
+
}
|
|
2430
|
+
return acc;
|
|
2431
|
+
}
|
|
2432
|
+
/** The subset of `all` whose `alias` is referenced by `nodes` (shallow — direct references only). */
|
|
2433
|
+
function referencedDataSources(nodes, all) {
|
|
2434
|
+
const aliases = collectReferencedAliases(nodes);
|
|
2435
|
+
return (all ?? []).filter(ds => aliases.has(ds?.alias));
|
|
2436
|
+
}
|
|
2437
|
+
/** Like `referencedDataSources`, but also pulls in the DERIVED dependencies (transitively): a
|
|
2438
|
+
* referenced `derived` alias needs its `request.derived.from` source resolved too, even though the
|
|
2439
|
+
* layout never names the parent directly. Use this when RESOLVING (the engine) so derived views
|
|
2440
|
+
* don't resolve to nothing. */
|
|
2441
|
+
function referencedDataSourcesDeep(nodes, all) {
|
|
2442
|
+
const byAlias = new Map((all ?? []).map(d => [d?.alias, d]));
|
|
2443
|
+
const wanted = collectReferencedAliases(nodes);
|
|
2444
|
+
const queue = [...wanted];
|
|
2445
|
+
while (queue.length) {
|
|
2446
|
+
const ds = byAlias.get(queue.pop());
|
|
2447
|
+
const from = ds?.request?.derived?.from;
|
|
2448
|
+
if (from && !wanted.has(from)) {
|
|
2449
|
+
wanted.add(from);
|
|
2450
|
+
queue.push(from);
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
return (all ?? []).filter(d => wanted.has(d?.alias));
|
|
2454
|
+
}
|
|
2413
2455
|
/**
|
|
2414
2456
|
* Turn a resolved {@link DataSourcePlan} into the concrete public HTTP request (url + params) — the
|
|
2415
2457
|
* SINGLE place that decides the URL shape, shared by the builder binding preview and the SSR engine
|
|
@@ -2434,6 +2476,169 @@ function dataSourcePlanToRequest(plan, endpoints) {
|
|
|
2434
2476
|
}
|
|
2435
2477
|
}
|
|
2436
2478
|
|
|
2479
|
+
/**
|
|
2480
|
+
* Shared RELATIVE (route-context) data resolving — the SINGLE source of truth for how a detail page
|
|
2481
|
+
* derives values FROM its current route item, consumed by BOTH the page builder (iox-cms-client) and
|
|
2482
|
+
* the SSR client engine (iox-client-engine). Companion to `data-source-resolver.ts`.
|
|
2483
|
+
*
|
|
2484
|
+
* These are the pure, transport-agnostic pieces behind the Route binding scope
|
|
2485
|
+
* (`$route` / `$current` / `$siblings` / `$related`). Resolution is a DEPENDENT SECOND PASS: the
|
|
2486
|
+
* consumer resolves `$current` first (the existing single by-id fetch via `planDataSource`), then
|
|
2487
|
+
* calls these to derive the relative values — there is NO new `DataSourcePlan` kind (`$related` still
|
|
2488
|
+
* feeds a normal `collection` plan built from {@link buildRelatedFilter}). See
|
|
2489
|
+
* `architecture/builder/relative-data-sources-plan.md`.
|
|
2490
|
+
*
|
|
2491
|
+
* There must be no second copy of this decision logic; only the transport (which HTTP layer fetches
|
|
2492
|
+
* the ordered collection / related list) differs between the two consumers.
|
|
2493
|
+
*/
|
|
2494
|
+
/** Aliases in the Route scope are platform-owned and start with this prefix; a USER alias may never
|
|
2495
|
+
* start with it, which keeps the Route scope structurally collision-proof with Local/Global. Used to
|
|
2496
|
+
* validate new/renamed aliases and to render the reserved (read-only) item state in the bind picker. */
|
|
2497
|
+
const RESERVED_ALIAS_PREFIX = '$';
|
|
2498
|
+
/** True for a platform-owned Route-scope alias (`$route`, `$current`, `$siblings`, `$related`, …). */
|
|
2499
|
+
function isReservedAlias(alias) {
|
|
2500
|
+
return typeof alias === 'string' && alias.startsWith(RESERVED_ALIAS_PREFIX);
|
|
2501
|
+
}
|
|
2502
|
+
const EMPTY_SIBLINGS = { prev: null, next: null, index: -1, count: 0, isFirst: false, isLast: false };
|
|
2503
|
+
/** Generic, deterministic comparator: numbers numerically, Dates by time, everything else by
|
|
2504
|
+
* locale-aware string compare (ISO date strings sort correctly this way). Nullish sorts first. */
|
|
2505
|
+
function compareValues(a, b) {
|
|
2506
|
+
if (a == null && b == null)
|
|
2507
|
+
return 0;
|
|
2508
|
+
if (a == null)
|
|
2509
|
+
return -1;
|
|
2510
|
+
if (b == null)
|
|
2511
|
+
return 1;
|
|
2512
|
+
if (typeof a === 'number' && typeof b === 'number')
|
|
2513
|
+
return a - b;
|
|
2514
|
+
const da = a instanceof Date ? a.getTime() : NaN;
|
|
2515
|
+
const db = b instanceof Date ? b.getTime() : NaN;
|
|
2516
|
+
if (!isNaN(da) && !isNaN(db))
|
|
2517
|
+
return da - db;
|
|
2518
|
+
return String(a).localeCompare(String(b));
|
|
2519
|
+
}
|
|
2520
|
+
/**
|
|
2521
|
+
* Locate the current item within an ordered set and return its neighbours + position. Pure: the input
|
|
2522
|
+
* list is never mutated (sorting works on a shallow copy). `currentId` is compared to each item's
|
|
2523
|
+
* `idField` via string equality (route-param values are strings). When the current id isn't found,
|
|
2524
|
+
* `index` is `-1` and `prev`/`next` are `null` (but `count` still reflects the set size).
|
|
2525
|
+
*/
|
|
2526
|
+
function resolveSiblings(items, currentId, options = {}) {
|
|
2527
|
+
const list = Array.isArray(items) ? items.slice() : [];
|
|
2528
|
+
const count = list.length;
|
|
2529
|
+
if (count === 0)
|
|
2530
|
+
return { ...EMPTY_SIBLINGS };
|
|
2531
|
+
const idField = options.idField ?? '_id';
|
|
2532
|
+
if (options.sortBy) {
|
|
2533
|
+
const dir = options.sortDir === 'desc' ? -1 : 1;
|
|
2534
|
+
list.sort((a, b) => dir * compareValues(getByPath(a, options.sortBy), getByPath(b, options.sortBy)));
|
|
2535
|
+
}
|
|
2536
|
+
const cid = currentId == null ? null : String(currentId);
|
|
2537
|
+
const index = cid == null ? -1 : list.findIndex(it => String(getByPath(it, idField)) === cid);
|
|
2538
|
+
if (index === -1)
|
|
2539
|
+
return { prev: null, next: null, index: -1, count, isFirst: false, isLast: false };
|
|
2540
|
+
const canWrap = !!options.wrap && count > 1;
|
|
2541
|
+
const prev = index > 0 ? list[index - 1] : (canWrap ? list[count - 1] : null);
|
|
2542
|
+
const next = index < count - 1 ? list[index + 1] : (canWrap ? list[0] : null);
|
|
2543
|
+
return { prev, next, index, count, isFirst: index === 0, isLast: index === count - 1 };
|
|
2544
|
+
}
|
|
2545
|
+
/** A value usable as an equality filter param: a non-empty scalar. Objects/arrays/nullish are skipped
|
|
2546
|
+
* so a related query isn't built from an unresolved rel object (surfaced as a config issue in the UI
|
|
2547
|
+
* rather than silently matching everything). Multi-value match is a v2 concern. */
|
|
2548
|
+
function isScalarFilterValue(v) {
|
|
2549
|
+
if (v == null)
|
|
2550
|
+
return false;
|
|
2551
|
+
if (typeof v === 'string')
|
|
2552
|
+
return v !== '';
|
|
2553
|
+
if (typeof v === 'number')
|
|
2554
|
+
return Number.isFinite(v);
|
|
2555
|
+
return typeof v === 'boolean';
|
|
2556
|
+
}
|
|
2557
|
+
/**
|
|
2558
|
+
* Build the collection filter for `$related` from the resolved current item: an equality match on
|
|
2559
|
+
* each `matchField`'s value (e.g. same `category`). Feeds a normal `collection` plan — no new plan
|
|
2560
|
+
* kind. Fields whose value is not a scalar (or is empty) are omitted, matching
|
|
2561
|
+
* `buildDataSourceFilter`'s "don't over-filter" rule. Exclude-self is applied to the RESULT via
|
|
2562
|
+
* {@link excludeSelf} (the public query controllers are equality-only, so it can't be a `$ne` param).
|
|
2563
|
+
*/
|
|
2564
|
+
function buildRelatedFilter(current, options) {
|
|
2565
|
+
const fields = Array.isArray(options.matchFields) ? options.matchFields : [options.matchFields];
|
|
2566
|
+
const filter = {};
|
|
2567
|
+
for (const field of fields) {
|
|
2568
|
+
if (!field)
|
|
2569
|
+
continue;
|
|
2570
|
+
const value = getByPath(current, field);
|
|
2571
|
+
if (isScalarFilterValue(value))
|
|
2572
|
+
filter[field] = value;
|
|
2573
|
+
}
|
|
2574
|
+
return filter;
|
|
2575
|
+
}
|
|
2576
|
+
/**
|
|
2577
|
+
* Drop the current item from a list by `idField` (default `_id`). Used to exclude self from a
|
|
2578
|
+
* `$related` result after fetch. A nullish/empty `currentId` leaves the list unchanged.
|
|
2579
|
+
*/
|
|
2580
|
+
function excludeSelf(items, currentId, idField = '_id') {
|
|
2581
|
+
if (!Array.isArray(items))
|
|
2582
|
+
return [];
|
|
2583
|
+
if (currentId == null || currentId === '')
|
|
2584
|
+
return items;
|
|
2585
|
+
const cid = String(currentId);
|
|
2586
|
+
return items.filter(it => String(getByPath(it, idField)) !== cid);
|
|
2587
|
+
}
|
|
2588
|
+
/** Resolve kinds in a fixed order so a `siblings`/`related` source may reference `$current` (or any
|
|
2589
|
+
* earlier relative alias) via `subjectFrom` and still see it resolved. */
|
|
2590
|
+
const KIND_ORDER = { route: 0, current: 1, siblings: 2, related: 3 };
|
|
2591
|
+
/** Does an item satisfy every field in an equality predicate map (string-compared)? */
|
|
2592
|
+
function matchesFilter(item, filter) {
|
|
2593
|
+
return Object.keys(filter).every(k => String(getByPath(item, k)) === String(filter[k]));
|
|
2594
|
+
}
|
|
2595
|
+
/**
|
|
2596
|
+
* Compute every `type:'relative'` source into `resolved` (keyed by alias), IN PLACE. Pure w.r.t. HTTP
|
|
2597
|
+
* — it only reads already-resolved roots + the route params, so both consumers run it identically as
|
|
2598
|
+
* a second pass. `route` → the route params; `current` → the subject source value; `siblings` →
|
|
2599
|
+
* {@link resolveSiblings} over the collection; `related` → the collection filtered client-side by
|
|
2600
|
+
* {@link buildRelatedFilter}'s predicate (+ {@link excludeSelf}). Mutates and returns `resolved`.
|
|
2601
|
+
*/
|
|
2602
|
+
function resolveRelativeInto(dataSources, resolved, routeParams = {}) {
|
|
2603
|
+
const relatives = (dataSources ?? [])
|
|
2604
|
+
.filter(d => d?.type === 'relative' && !!d.alias && !!d.request?.relative)
|
|
2605
|
+
.sort((a, b) => KIND_ORDER[a.request.relative.kind] - KIND_ORDER[b.request.relative.kind]);
|
|
2606
|
+
for (const d of relatives) {
|
|
2607
|
+
const alias = d.alias;
|
|
2608
|
+
const spec = d.request.relative;
|
|
2609
|
+
const idField = spec.idField ?? '_id';
|
|
2610
|
+
switch (spec.kind) {
|
|
2611
|
+
case 'route':
|
|
2612
|
+
resolved[alias] = routeParams;
|
|
2613
|
+
break;
|
|
2614
|
+
case 'current':
|
|
2615
|
+
resolved[alias] = spec.from ? (resolved[spec.from] ?? null) : null;
|
|
2616
|
+
break;
|
|
2617
|
+
case 'siblings': {
|
|
2618
|
+
const collection = resolved[spec.from ?? ''];
|
|
2619
|
+
const subject = resolved[spec.subjectFrom ?? spec.from ?? ''];
|
|
2620
|
+
const currentId = getByPath(subject, idField);
|
|
2621
|
+
resolved[alias] = resolveSiblings(collection, currentId, {
|
|
2622
|
+
idField, sortBy: spec.sortBy, sortDir: spec.sortDir, wrap: spec.wrap,
|
|
2623
|
+
});
|
|
2624
|
+
break;
|
|
2625
|
+
}
|
|
2626
|
+
case 'related': {
|
|
2627
|
+
const collection = resolved[spec.from ?? ''];
|
|
2628
|
+
const subject = resolved[spec.subjectFrom ?? spec.from ?? ''];
|
|
2629
|
+
const filter = buildRelatedFilter(subject, { matchFields: spec.matchFields ?? [] });
|
|
2630
|
+
const pool = Array.isArray(collection) ? collection : [];
|
|
2631
|
+
let items = pool.filter(it => matchesFilter(it, filter));
|
|
2632
|
+
if (spec.excludeSelf !== false)
|
|
2633
|
+
items = excludeSelf(items, getByPath(subject, idField), idField);
|
|
2634
|
+
resolved[alias] = items;
|
|
2635
|
+
break;
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
return resolved;
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2437
2642
|
/**
|
|
2438
2643
|
* Cinematic page-transition presets — the SINGLE SOURCE OF TRUTH shared by the page builder
|
|
2439
2644
|
* (preview) and the SSR client engine (production). Pure, transport-agnostic: this module only
|
|
@@ -2868,5 +3073,5 @@ function effectiveTransitionId(anim) {
|
|
|
2868
3073
|
* Generated bundle index. Do not edit.
|
|
2869
3074
|
*/
|
|
2870
3075
|
|
|
2871
|
-
export { AOSService, AnalyticsService, BuilderButtonComponent, BuilderDividerComponent, BuilderHeadingComponent, BuilderIconComponent, BuilderImageComponent, BuilderLinkComponent, BuilderSpacerComponent, ButtonBlockComponent, CMSClientInfraModule, CardComponent, ClientListComponent, ClientListItemComponent, ClientRepeaterComponent, ClientSliderContainerComponent, ClientSliderSlideComponent, ComponentInstanceRegistryService, ConsentService, ContainerComponent, ContentService, DEFAULT_PAGE_TRANSITION, DEFAULT_PAGE_TRANSITION_DURATION, DEFAULT_PAGE_TRANSITION_EASING, DEFAULT_PAGE_TRANSITION_PERSPECTIVE, DEFAULT_WIDTH_BY_TYPE, ENVIRONMENT, IOX_BUILDER_EVENTS, IS_PREVIEW, IoxAnimateContainer, IoxAosDirective, IoxAosModule, IoxBuilderComponentsModule, IoxComponentRegistryService, IoxPageComponent, IoxPageModule, IoxUiModule, LinkedContainerComponent, PAGE_TRANSITION_CATEGORIES, PAGE_TRANSITION_PRESETS, PageScrollProvider, PrivacyModalComponent, PrivacyModalService, PrivacyModelEvent, SUPPRESSED_STYLE_PROPS_BY_TYPE, SectionComponent, TRANSITION_FEELS, TextBlockComponent, VIRTUAL_TRAIT_KEYS, ViewPositionDirective, ViewPositionModule, WebSettingsService, applyFeel, buildDataSourceFilter, buildTransitionTimeline, compileDeclarations, composeVirtualTraits, dataSourcePlanToRequest, effectiveTransitionId, feelFor, getByPath, legacyEnterToTransition, matchRouteParams, normalizeCollectionResult, planDataSource, renderDefaultDeclarations, resolveDerivedInto, resolvePageTransition, resolveRepeaterItems, resolveSingleItemId, rewriteViewportUnits, stripSuppressedProps, styleKeyToKebab };
|
|
3076
|
+
export { AOSService, AnalyticsService, BuilderButtonComponent, BuilderDividerComponent, BuilderHeadingComponent, BuilderIconComponent, BuilderImageComponent, BuilderLinkComponent, BuilderSpacerComponent, ButtonBlockComponent, CMSClientInfraModule, CardComponent, ClientListComponent, ClientListItemComponent, ClientRepeaterComponent, ClientSliderContainerComponent, ClientSliderSlideComponent, ComponentInstanceRegistryService, ConsentService, ContainerComponent, ContentService, DEFAULT_PAGE_TRANSITION, DEFAULT_PAGE_TRANSITION_DURATION, DEFAULT_PAGE_TRANSITION_EASING, DEFAULT_PAGE_TRANSITION_PERSPECTIVE, DEFAULT_WIDTH_BY_TYPE, ENVIRONMENT, IOX_BUILDER_EVENTS, IS_PREVIEW, IoxAnimateContainer, IoxAosDirective, IoxAosModule, IoxBuilderComponentsModule, IoxComponentRegistryService, IoxPageComponent, IoxPageModule, IoxUiModule, LinkedContainerComponent, PAGE_TRANSITION_CATEGORIES, PAGE_TRANSITION_PRESETS, PageScrollProvider, PrivacyModalComponent, PrivacyModalService, PrivacyModelEvent, RESERVED_ALIAS_PREFIX, SUPPRESSED_STYLE_PROPS_BY_TYPE, SectionComponent, TRANSITION_FEELS, TextBlockComponent, VIRTUAL_TRAIT_KEYS, ViewPositionDirective, ViewPositionModule, WebSettingsService, applyFeel, buildDataSourceFilter, buildRelatedFilter, buildTransitionTimeline, collectReferencedAliases, compileDeclarations, composeVirtualTraits, dataSourcePlanToRequest, effectiveTransitionId, excludeSelf, feelFor, getByPath, isReservedAlias, legacyEnterToTransition, matchRouteParams, normalizeCollectionResult, planDataSource, referencedDataSources, referencedDataSourcesDeep, renderDefaultDeclarations, resolveDerivedInto, resolvePageTransition, resolveRelativeInto, resolveRepeaterItems, resolveSiblings, resolveSingleItemId, rewriteViewportUnits, stripSuppressedProps, styleKeyToKebab };
|
|
2872
3077
|
//# sourceMappingURL=vectoriox-iox-ui.mjs.map
|