@vectoriox/iox-ui 4.4.4 → 4.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2326,6 +2326,244 @@ function dataSourcePlanToRequest(plan, endpoints) {
2326
2326
  }
2327
2327
  }
2328
2328
 
2329
+ /**
2330
+ * Cinematic page-transition presets — the SINGLE SOURCE OF TRUTH shared by the page builder
2331
+ * (preview) and the SSR client engine (production). Pure, transport-agnostic: this module only
2332
+ * describes WAAPI keyframe pairs + timing/ordering. It never touches the DOM.
2333
+ *
2334
+ * A page transition plays TWO synchronized WAAPI animations at once — an `out` animation on a
2335
+ * snapshot of the outgoing page and an `in` animation on the incoming page — inside a shared
2336
+ * `perspective` container, gated on BOTH finishing (the "cinematic" feel, à la Codrops
2337
+ * PageTransitions). The consumer supplies the DOM (snapshot layer + live page) and calls
2338
+ * `element.animate(frames, options)`; it does NOT redefine the frames.
2339
+ *
2340
+ * @see architecture/builder/page-transitions.md
2341
+ */
2342
+ /** Perspective (px) applied to the transition container unless a preset overrides it. */
2343
+ const DEFAULT_PAGE_TRANSITION_PERSPECTIVE = 1200;
2344
+ /** Default transition duration (ms) when the page does not specify one. */
2345
+ const DEFAULT_PAGE_TRANSITION_DURATION = 600;
2346
+ // ── Keyframe helpers ────────────────────────────────────────────────────────
2347
+ const FADE_OUT = [{ opacity: 1 }, { opacity: 0 }];
2348
+ const FADE_IN = [{ opacity: 0 }, { opacity: 1 }];
2349
+ const move = (from, to) => [{ transform: from }, { transform: to }];
2350
+ // ── The preset catalog ──────────────────────────────────────────────────────
2351
+ // Keyed by a stable preset id persisted in pageSettings.routeAnimation.transition.
2352
+ const PAGE_TRANSITION_PRESETS = {
2353
+ // ── Move (both pages slide; no fade — the classic Codrops "move") ──────────
2354
+ 'move-left': {
2355
+ category: 'move', label: 'Move left / from right',
2356
+ outKeyframes: move('translateX(0)', 'translateX(-100%)'),
2357
+ inKeyframes: move('translateX(100%)', 'translateX(0)'),
2358
+ },
2359
+ 'move-right': {
2360
+ category: 'move', label: 'Move right / from left',
2361
+ outKeyframes: move('translateX(0)', 'translateX(100%)'),
2362
+ inKeyframes: move('translateX(-100%)', 'translateX(0)'),
2363
+ },
2364
+ 'move-up': {
2365
+ category: 'move', label: 'Move up / from bottom',
2366
+ outKeyframes: move('translateY(0)', 'translateY(-100%)'),
2367
+ inKeyframes: move('translateY(100%)', 'translateY(0)'),
2368
+ },
2369
+ 'move-down': {
2370
+ category: 'move', label: 'Move down / from top',
2371
+ outKeyframes: move('translateY(0)', 'translateY(100%)'),
2372
+ inKeyframes: move('translateY(-100%)', 'translateY(0)'),
2373
+ },
2374
+ // ── Fade (incoming sits on top; outgoing dissolves or holds) ───────────────
2375
+ 'fade': {
2376
+ category: 'fade', label: 'Fade',
2377
+ outKeyframes: FADE_OUT, inKeyframes: FADE_IN, ontop: 'in',
2378
+ },
2379
+ 'fade-from-right': {
2380
+ category: 'fade', label: 'Fade / from right',
2381
+ outKeyframes: FADE_OUT,
2382
+ inKeyframes: [{ transform: 'translateX(100%)', opacity: 0 }, { transform: 'translateX(0)', opacity: 1 }],
2383
+ ontop: 'in',
2384
+ },
2385
+ 'fade-from-left': {
2386
+ category: 'fade', label: 'Fade / from left',
2387
+ outKeyframes: FADE_OUT,
2388
+ inKeyframes: [{ transform: 'translateX(-100%)', opacity: 0 }, { transform: 'translateX(0)', opacity: 1 }],
2389
+ ontop: 'in',
2390
+ },
2391
+ 'fade-from-bottom': {
2392
+ category: 'fade', label: 'Fade / from bottom',
2393
+ outKeyframes: FADE_OUT,
2394
+ inKeyframes: [{ transform: 'translateY(100%)', opacity: 0 }, { transform: 'translateY(0)', opacity: 1 }],
2395
+ ontop: 'in',
2396
+ },
2397
+ // ── Scale ──────────────────────────────────────────────────────────────────
2398
+ 'scale-down-up': {
2399
+ category: 'scale', label: 'Scale down / scale up',
2400
+ outKeyframes: [{ transform: 'scale(1)', opacity: 1 }, { transform: 'scale(0.8)', opacity: 0 }],
2401
+ inKeyframes: [{ transform: 'scale(1.2)', opacity: 0 }, { transform: 'scale(1)', opacity: 1 }],
2402
+ ontop: 'in',
2403
+ },
2404
+ 'scale-down': {
2405
+ category: 'scale', label: 'Scale down / from right',
2406
+ outKeyframes: [{ transform: 'scale(1)', opacity: 1 }, { transform: 'scale(0.8)', opacity: 0 }],
2407
+ inKeyframes: move('translateX(100%)', 'translateX(0)'),
2408
+ ontop: 'in',
2409
+ },
2410
+ 'scale-up': {
2411
+ category: 'scale', label: 'Move to left / scale up',
2412
+ outKeyframes: move('translateX(0)', 'translateX(-100%)'),
2413
+ inKeyframes: [{ transform: 'scale(0.8)', opacity: 0 }, { transform: 'scale(1)', opacity: 1 }],
2414
+ ontop: 'out',
2415
+ },
2416
+ // ── Rotate / 3D (the cinematic "wow" family) ───────────────────────────────
2417
+ 'flip': {
2418
+ category: 'rotate', label: 'Flip',
2419
+ outKeyframes: [{ transform: 'rotateY(0deg)', opacity: 1 }, { transform: 'rotateY(-180deg)', opacity: 0 }],
2420
+ inKeyframes: [{ transform: 'rotateY(180deg)', opacity: 0 }, { transform: 'rotateY(0deg)', opacity: 1 }],
2421
+ outTransformOrigin: '50% 50%', inTransformOrigin: '50% 50%',
2422
+ },
2423
+ 'cube-left': {
2424
+ category: 'rotate', label: 'Cube / to left',
2425
+ outKeyframes: [{ transform: 'translateZ(0) rotateY(0deg)', opacity: 1 }, { transform: 'translateZ(-800px) rotateY(-90deg)', opacity: 0.3 }],
2426
+ inKeyframes: [{ transform: 'translateZ(-800px) rotateY(90deg)', opacity: 0.3 }, { transform: 'translateZ(0) rotateY(0deg)', opacity: 1 }],
2427
+ outTransformOrigin: '50% 50%', inTransformOrigin: '50% 50%',
2428
+ },
2429
+ 'cube-right': {
2430
+ category: 'rotate', label: 'Cube / to right',
2431
+ outKeyframes: [{ transform: 'translateZ(0) rotateY(0deg)', opacity: 1 }, { transform: 'translateZ(-800px) rotateY(90deg)', opacity: 0.3 }],
2432
+ inKeyframes: [{ transform: 'translateZ(-800px) rotateY(-90deg)', opacity: 0.3 }, { transform: 'translateZ(0) rotateY(0deg)', opacity: 1 }],
2433
+ outTransformOrigin: '50% 50%', inTransformOrigin: '50% 50%',
2434
+ },
2435
+ 'carousel-left': {
2436
+ category: 'rotate', label: 'Carousel / to left',
2437
+ outKeyframes: [{ transform: 'translateX(0) scale(1)', opacity: 1 }, { transform: 'translateX(-150%) scale(0.4)', opacity: 0.3 }],
2438
+ inKeyframes: [{ transform: 'translateX(200%) scale(0.4)', opacity: 0.3 }, { transform: 'translateX(0) scale(1)', opacity: 1 }],
2439
+ ontop: 'in',
2440
+ },
2441
+ 'carousel-right': {
2442
+ category: 'rotate', label: 'Carousel / to right',
2443
+ outKeyframes: [{ transform: 'translateX(0) scale(1)', opacity: 1 }, { transform: 'translateX(150%) scale(0.4)', opacity: 0.3 }],
2444
+ inKeyframes: [{ transform: 'translateX(-200%) scale(0.4)', opacity: 0.3 }, { transform: 'translateX(0) scale(1)', opacity: 1 }],
2445
+ ontop: 'in',
2446
+ },
2447
+ 'fall': {
2448
+ category: 'rotate', label: 'Fall',
2449
+ outKeyframes: [{ transform: 'translateZ(0) rotateX(0deg)', opacity: 1 }, { transform: 'translateZ(-500px) rotateX(90deg)', opacity: 0 }],
2450
+ inKeyframes: FADE_IN,
2451
+ outTransformOrigin: '50% 50%', ontop: 'out', inDelayRatio: 0.3,
2452
+ },
2453
+ 'newspaper': {
2454
+ category: 'rotate', label: 'Newspaper',
2455
+ outKeyframes: [{ transform: 'scale(1) rotate(0deg)', opacity: 1 }, { transform: 'scale(0) rotate(720deg)', opacity: 0 }],
2456
+ inKeyframes: [{ transform: 'scale(0) rotate(-720deg)', opacity: 0 }, { transform: 'scale(1) rotate(0deg)', opacity: 1 }],
2457
+ ontop: 'in', inDelayRatio: 0.5,
2458
+ },
2459
+ 'fold-left': {
2460
+ category: 'rotate', label: 'Fold / unfold',
2461
+ outKeyframes: [{ transform: 'translateX(0) rotateY(0deg)', opacity: 1 }, { transform: 'translateX(-100%) rotateY(-90deg)', opacity: 0.6 }],
2462
+ inKeyframes: [{ transform: 'translateX(100%) rotateY(90deg)', opacity: 0.6 }, { transform: 'translateX(0) rotateY(0deg)', opacity: 1 }],
2463
+ outTransformOrigin: '0% 50%', inTransformOrigin: '100% 50%', ontop: 'in',
2464
+ },
2465
+ 'room-left': {
2466
+ category: 'rotate', label: 'Room / to left',
2467
+ outKeyframes: [{ transform: 'translateX(0) rotateY(0deg)', opacity: 1 }, { transform: 'translateX(-100%) rotateY(90deg)', opacity: 0.4 }],
2468
+ inKeyframes: [{ transform: 'translateX(100%) rotateY(-90deg)', opacity: 0.4 }, { transform: 'translateX(0) rotateY(0deg)', opacity: 1 }],
2469
+ outTransformOrigin: '100% 50%', inTransformOrigin: '0% 50%',
2470
+ },
2471
+ };
2472
+ /** Built from PAGE_TRANSITION_PRESETS so the two lists can never drift apart. */
2473
+ const PAGE_TRANSITION_CATEGORIES = (() => {
2474
+ const order = [
2475
+ { value: 'move', label: 'Move' },
2476
+ { value: 'fade', label: 'Fade' },
2477
+ { value: 'scale', label: 'Scale' },
2478
+ { value: 'rotate', label: 'Rotate / 3D' },
2479
+ ];
2480
+ return order.map(cat => ({
2481
+ ...cat,
2482
+ presets: Object.entries(PAGE_TRANSITION_PRESETS)
2483
+ .filter(([, p]) => p.category === cat.value)
2484
+ .map(([value, p]) => ({ value, label: p.label })),
2485
+ }));
2486
+ })();
2487
+ /**
2488
+ * Resolve a persisted preset id + timing into the two WAAPI animations the consumer runs.
2489
+ * Returns `null` for unknown / 'none' presets (caller should skip the transition).
2490
+ */
2491
+ function resolvePageTransition(name, opts = {}) {
2492
+ if (!name || name === 'none')
2493
+ return null;
2494
+ const preset = PAGE_TRANSITION_PRESETS[name];
2495
+ if (!preset)
2496
+ return null;
2497
+ const duration = opts.duration ?? DEFAULT_PAGE_TRANSITION_DURATION;
2498
+ const easing = opts.easing ?? 'ease';
2499
+ const perspective = preset.perspective ?? opts.perspective ?? DEFAULT_PAGE_TRANSITION_PERSPECTIVE;
2500
+ const clampRatio = (r) => Math.max(0, Math.min(1, r ?? 0));
2501
+ const outDelay = clampRatio(preset.outDelayRatio) * duration;
2502
+ const inDelay = clampRatio(preset.inDelayRatio) * duration;
2503
+ return {
2504
+ outFrames: preset.outKeyframes,
2505
+ inFrames: preset.inKeyframes,
2506
+ // OUT plays on a throwaway snapshot → 'both' (hold keyframe[0] during delay, keyframe[last] after).
2507
+ outOptions: { duration, easing, delay: outDelay, fill: 'both' },
2508
+ // IN plays on the LIVE page host, so it must NOT persist its end state: a lingering `transform`
2509
+ // makes that host a containing block for absolutely-positioned descendants, collapsing a
2510
+ // `position:absolute; inset:0` page shell (iox-page-component) to 0 height. Every `in` preset
2511
+ // ends at identity (translate 0 / scale 1 / opacity 1), so 'backwards' — which only holds
2512
+ // keyframe[0] during the delay — is visually identical and leaves no residue.
2513
+ inOptions: { duration, easing, delay: inDelay, fill: 'backwards' },
2514
+ ontop: preset.ontop ?? 'in',
2515
+ perspective,
2516
+ outTransformOrigin: preset.outTransformOrigin,
2517
+ inTransformOrigin: preset.inTransformOrigin,
2518
+ };
2519
+ }
2520
+ /**
2521
+ * The ordered phases a navigation runs, given what is present. The order is INVARIANT:
2522
+ * every element `leave` animation finishes before the page `transition` starts, and the page
2523
+ * `transition` finishes before any element `enter` animation begins. Phases with nothing to
2524
+ * play are omitted. The engine iterates this list and `await`s each phase's completion.
2525
+ */
2526
+ function buildTransitionTimeline(input) {
2527
+ const phases = [];
2528
+ if (input.hasLeave)
2529
+ phases.push('leave');
2530
+ if (input.hasTransition)
2531
+ phases.push('transition');
2532
+ if (input.hasEnter)
2533
+ phases.push('enter');
2534
+ return phases;
2535
+ }
2536
+ // ── Back-compat migration from the legacy single-page route-animation presets ─
2537
+ /**
2538
+ * Map a legacy `enter` preset (none/fade/slideUp/slideDown/zoomIn/zoomOut/blurIn/flip) to the
2539
+ * nearest cinematic paired `transition` id, so pages saved before this system keep animating.
2540
+ * Returns 'fade' for anything unmapped, and 'none' for 'none'.
2541
+ */
2542
+ function legacyEnterToTransition(enter) {
2543
+ switch (enter) {
2544
+ case 'none': return 'none';
2545
+ case 'slideUp': return 'fade-from-bottom';
2546
+ case 'slideDown': return 'fade';
2547
+ case 'zoomIn': return 'scale-down-up';
2548
+ case 'zoomOut': return 'scale-down-up';
2549
+ case 'blurIn': return 'fade';
2550
+ case 'flip': return 'flip';
2551
+ case 'fade': return 'fade';
2552
+ default: return 'fade';
2553
+ }
2554
+ }
2555
+ /**
2556
+ * Resolve the effective transition id for a page's route-animation settings: the explicit
2557
+ * `transition` when present, otherwise migrated from the legacy `enter` preset.
2558
+ */
2559
+ function effectiveTransitionId(anim) {
2560
+ if (!anim)
2561
+ return 'fade';
2562
+ if (anim.transition)
2563
+ return anim.transition;
2564
+ return legacyEnterToTransition(anim.enter);
2565
+ }
2566
+
2329
2567
  /*
2330
2568
  * Public API Surface of iox-ui
2331
2569
  */
@@ -2334,5 +2572,5 @@ function dataSourcePlanToRequest(plan, endpoints) {
2334
2572
  * Generated bundle index. Do not edit.
2335
2573
  */
2336
2574
 
2337
- export { AOSService, AnalyticsService, BuilderButtonComponent, BuilderDividerComponent, BuilderHeadingComponent, BuilderIconComponent, BuilderImageComponent, BuilderLinkComponent, BuilderSpacerComponent, ButtonBlockComponent, CMSClientInfraModule, CardComponent, ClientListComponent, ClientListItemComponent, ClientRepeaterComponent, ClientSliderContainerComponent, ClientSliderSlideComponent, ComponentInstanceRegistryService, ConsentService, ContainerComponent, ContentService, DEFAULT_WIDTH_BY_TYPE, ENVIRONMENT, IOX_BUILDER_EVENTS, IS_PREVIEW, IoxAnimateContainer, IoxAosDirective, IoxAosModule, IoxBuilderComponentsModule, IoxComponentRegistryService, IoxPageComponent, IoxPageModule, IoxUiModule, LinkedContainerComponent, PageScrollProvider, PrivacyModalComponent, PrivacyModalService, PrivacyModelEvent, SectionComponent, TextBlockComponent, VIRTUAL_TRAIT_KEYS, ViewPositionDirective, ViewPositionModule, WebSettingsService, buildDataSourceFilter, compileDeclarations, composeVirtualTraits, dataSourcePlanToRequest, matchRouteParams, normalizeCollectionResult, planDataSource, renderDefaultDeclarations, resolveSingleItemId, rewriteViewportUnits, styleKeyToKebab };
2575
+ 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_DURATION, 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, SectionComponent, TextBlockComponent, VIRTUAL_TRAIT_KEYS, ViewPositionDirective, ViewPositionModule, WebSettingsService, buildDataSourceFilter, buildTransitionTimeline, compileDeclarations, composeVirtualTraits, dataSourcePlanToRequest, effectiveTransitionId, legacyEnterToTransition, matchRouteParams, normalizeCollectionResult, planDataSource, renderDefaultDeclarations, resolvePageTransition, resolveSingleItemId, rewriteViewportUnits, styleKeyToKebab };
2338
2576
  //# sourceMappingURL=vectoriox-iox-ui.mjs.map