@mohamedatia/fly-design-system 2.6.4 → 2.7.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.
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, signal, computed, Injectable, inject, ErrorHandler, PLATFORM_ID, Pipe, DOCUMENT, ElementRef, input, output, HostListener, ViewChild,
|
|
2
|
+
import { InjectionToken, signal, computed, Injectable, inject, ErrorHandler, PLATFORM_ID, DestroyRef, ChangeDetectionStrategy, Component, Pipe, DOCUMENT, ElementRef, input, output, HostListener, ViewChild, EventEmitter, Output, Input, forwardRef, Injector, viewChild, effect, afterNextRender, ViewEncapsulation, model, Directive } from '@angular/core';
|
|
3
3
|
import * as i1$1 from '@angular/common';
|
|
4
|
-
import { isPlatformBrowser, CommonModule, DOCUMENT as DOCUMENT$1 } from '@angular/common';
|
|
4
|
+
import { isPlatformBrowser, NgComponentOutlet, CommonModule, DOCUMENT as DOCUMENT$1 } from '@angular/common';
|
|
5
5
|
import { Router, NavigationEnd } from '@angular/router';
|
|
6
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
6
7
|
import { of, ReplaySubject, Subject } from 'rxjs';
|
|
7
8
|
import * as i1 from '@angular/forms';
|
|
8
9
|
import { FormsModule, NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
|
|
9
|
-
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
10
10
|
import { switchMap, debounceTime, distinctUntilChanged, filter } from 'rxjs/operators';
|
|
11
11
|
import { HttpClient, HttpEventType } from '@angular/common/http';
|
|
12
12
|
import Cropper from 'cropperjs';
|
|
@@ -375,6 +375,52 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
375
375
|
type: Injectable
|
|
376
376
|
}] });
|
|
377
377
|
|
|
378
|
+
/**
|
|
379
|
+
* Optional injection token a remote provides at its root to declare which routes
|
|
380
|
+
* `<fly-remote-router-outlet>` should resolve. Provided WITHIN the remote (not
|
|
381
|
+
* the shell), e.g.:
|
|
382
|
+
*
|
|
383
|
+
* ```ts
|
|
384
|
+
* @Component({
|
|
385
|
+
* providers: [
|
|
386
|
+
* FlyRemoteRouter,
|
|
387
|
+
* { provide: FLY_REMOTE_ROUTES, useValue: CIRCLES_ROUTES },
|
|
388
|
+
* ],
|
|
389
|
+
* })
|
|
390
|
+
* export class CirclesComponent { }
|
|
391
|
+
* ```
|
|
392
|
+
*
|
|
393
|
+
* If no routes are provided, `FlyRemoteRouter.matchedRoute()` is always `null`
|
|
394
|
+
* and the outlet renders nothing — useful for remotes that don't need internal
|
|
395
|
+
* routing.
|
|
396
|
+
*/
|
|
397
|
+
const FLY_REMOTE_ROUTES = new InjectionToken('FLY_REMOTE_ROUTES');
|
|
398
|
+
/**
|
|
399
|
+
* Match a route's path pattern against URL segments. Returns the captured params
|
|
400
|
+
* map on a successful match, or `null` if the pattern can't match. Empty path
|
|
401
|
+
* matches only an empty segments array.
|
|
402
|
+
*
|
|
403
|
+
* Exported for unit testing — most consumers should rely on
|
|
404
|
+
* `FlyRemoteRouter.matchedRoute()`.
|
|
405
|
+
*/
|
|
406
|
+
function matchFlyRoutePattern(pattern, segments) {
|
|
407
|
+
const patternSegments = pattern.split('/').filter(Boolean);
|
|
408
|
+
if (patternSegments.length !== segments.length)
|
|
409
|
+
return null;
|
|
410
|
+
const params = {};
|
|
411
|
+
for (let i = 0; i < patternSegments.length; i++) {
|
|
412
|
+
const p = patternSegments[i];
|
|
413
|
+
const s = segments[i];
|
|
414
|
+
if (p.startsWith(':')) {
|
|
415
|
+
params[p.slice(1)] = decodeURIComponent(s);
|
|
416
|
+
}
|
|
417
|
+
else if (p !== s) {
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return params;
|
|
422
|
+
}
|
|
423
|
+
|
|
378
424
|
/**
|
|
379
425
|
* FlyOS standard navigation surface for Business / Supporting App remotes.
|
|
380
426
|
*
|
|
@@ -437,6 +483,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
437
483
|
class FlyRemoteRouter {
|
|
438
484
|
windowData = inject(WINDOW_DATA, { optional: true });
|
|
439
485
|
router = inject(Router, { optional: true });
|
|
486
|
+
/**
|
|
487
|
+
* Optional route table — if the remote provides `FLY_REMOTE_ROUTES`,
|
|
488
|
+
* `matchedRoute` / `params` resolve against it. Unset means the consumer is
|
|
489
|
+
* driving its own switch/template — `matchedRoute` stays `null`.
|
|
490
|
+
*/
|
|
491
|
+
routes = inject(FLY_REMOTE_ROUTES, { optional: true }) ?? [];
|
|
440
492
|
/**
|
|
441
493
|
* True when this remote is rendered inside the FlyOS desktop shell.
|
|
442
494
|
*
|
|
@@ -469,17 +521,69 @@ class FlyRemoteRouter {
|
|
|
469
521
|
const u = this._url();
|
|
470
522
|
return u.split('?')[0].split('#')[0].split('/').filter(Boolean);
|
|
471
523
|
}, ...(ngDevMode ? [{ debugName: "segments" }] : /* istanbul ignore next */ []));
|
|
524
|
+
/**
|
|
525
|
+
* First route from `FLY_REMOTE_ROUTES` whose pattern matches `segments()`,
|
|
526
|
+
* paired with its captured params. `null` when no routes are registered or
|
|
527
|
+
* none matches. Used by `<fly-remote-router-outlet>` to pick what to render.
|
|
528
|
+
*
|
|
529
|
+
* Match order: routes are tried in declaration order. Put more specific
|
|
530
|
+
* patterns (e.g. `'signals/:id'`) before catch-alls.
|
|
531
|
+
*/
|
|
532
|
+
matchedRoute = computed(() => {
|
|
533
|
+
const segs = this.segments();
|
|
534
|
+
for (const route of this.routes) {
|
|
535
|
+
const params = matchFlyRoutePattern(route.path, segs);
|
|
536
|
+
if (params != null)
|
|
537
|
+
return { route, params };
|
|
538
|
+
}
|
|
539
|
+
return null;
|
|
540
|
+
}, ...(ngDevMode ? [{ debugName: "matchedRoute" }] : /* istanbul ignore next */ []));
|
|
541
|
+
/**
|
|
542
|
+
* Captured route params from the active route, e.g. `{ id: 'abc' }` for a
|
|
543
|
+
* `'signals/:id'` match on `/signals/abc`. Empty object if no route matched
|
|
544
|
+
* or the matched route has no captures.
|
|
545
|
+
*
|
|
546
|
+
* Components can read this directly:
|
|
547
|
+
* ```ts
|
|
548
|
+
* private readonly flyRouter = inject(FlyRemoteRouter);
|
|
549
|
+
* readonly signalId = computed(() => this.flyRouter.params()['id'] ?? '');
|
|
550
|
+
* ```
|
|
551
|
+
*/
|
|
552
|
+
params = computed(() => this.matchedRoute()?.params ?? {}, ...(ngDevMode ? [{ debugName: "params" }] : /* istanbul ignore next */ []));
|
|
553
|
+
destroyRef = inject(DestroyRef);
|
|
472
554
|
constructor() {
|
|
473
555
|
if (!this.isEmbedded && this.router) {
|
|
474
|
-
//
|
|
475
|
-
//
|
|
556
|
+
// Standalone: seed from Angular Router and track NavigationEnd events so
|
|
557
|
+
// browser back/forward (which the host Router handles) keep segments() current.
|
|
476
558
|
this._url.set(this.router.url);
|
|
477
|
-
this.router.events
|
|
559
|
+
this.router.events
|
|
560
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
561
|
+
.subscribe(event => {
|
|
478
562
|
if (event instanceof NavigationEnd) {
|
|
479
563
|
this._url.set(event.urlAfterRedirects);
|
|
480
564
|
}
|
|
481
565
|
});
|
|
482
566
|
}
|
|
567
|
+
else if (this.isEmbedded) {
|
|
568
|
+
// Embedded: seed from current browser location on initial load.
|
|
569
|
+
if (typeof window !== 'undefined') {
|
|
570
|
+
this._url.set(window.location.pathname || '/');
|
|
571
|
+
// Listen for browser back/forward so segments() stays in sync with
|
|
572
|
+
// history entries pushed by navigate() / navigateByUrl().
|
|
573
|
+
const onPopState = (event) => {
|
|
574
|
+
const state = event.state;
|
|
575
|
+
// Prefer the URL we stashed in the history state; fall back to pathname.
|
|
576
|
+
const url = state?.flyRemoteUrl ?? window.location.pathname ?? '/';
|
|
577
|
+
this._url.set(url.startsWith('/') ? url : '/' + url);
|
|
578
|
+
};
|
|
579
|
+
window.addEventListener('popstate', onPopState);
|
|
580
|
+
// Defensive cleanup — the service is providedIn: 'root' so this fires
|
|
581
|
+
// only when the whole Angular app is destroyed, but it keeps things tidy.
|
|
582
|
+
this.destroyRef.onDestroy(() => {
|
|
583
|
+
window.removeEventListener('popstate', onPopState);
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
}
|
|
483
587
|
}
|
|
484
588
|
/**
|
|
485
589
|
* Navigate to a route, identified by an array of commands as you would pass
|
|
@@ -488,9 +592,11 @@ class FlyRemoteRouter {
|
|
|
488
592
|
*/
|
|
489
593
|
navigate(commands, extras) {
|
|
490
594
|
if (!this.isEmbedded && this.router) {
|
|
595
|
+
// Standalone: Angular Router owns history — pushState semantics are the default.
|
|
491
596
|
return this.router.navigate(commands, extras);
|
|
492
597
|
}
|
|
493
|
-
this.
|
|
598
|
+
const url = this.buildUrl(commands);
|
|
599
|
+
this._pushEmbedded(url);
|
|
494
600
|
return Promise.resolve(true);
|
|
495
601
|
}
|
|
496
602
|
/**
|
|
@@ -498,9 +604,10 @@ class FlyRemoteRouter {
|
|
|
498
604
|
*/
|
|
499
605
|
navigateByUrl(url, extras) {
|
|
500
606
|
if (!this.isEmbedded && this.router) {
|
|
607
|
+
// Standalone: Angular Router owns history — pushState semantics are the default.
|
|
501
608
|
return this.router.navigateByUrl(url, extras);
|
|
502
609
|
}
|
|
503
|
-
this.
|
|
610
|
+
this._pushEmbedded(url.startsWith('/') ? url : '/' + url);
|
|
504
611
|
return Promise.resolve(true);
|
|
505
612
|
}
|
|
506
613
|
/**
|
|
@@ -509,16 +616,26 @@ class FlyRemoteRouter {
|
|
|
509
616
|
* so the browser's back stack is respected.
|
|
510
617
|
*/
|
|
511
618
|
back() {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
619
|
+
// Both modes: delegate to the browser's history stack. In embedded mode we
|
|
620
|
+
// now push real history entries in navigate() / navigateByUrl(), so
|
|
621
|
+
// history.back() triggers popstate and the listener updates segments().
|
|
622
|
+
if (typeof history !== 'undefined') {
|
|
623
|
+
history.back();
|
|
516
624
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* In embedded mode: push a real browser history entry (so back/forward work)
|
|
628
|
+
* and update the internal signal synchronously.
|
|
629
|
+
*
|
|
630
|
+
* The state object carries `flyRemoteUrl` so the popstate listener can
|
|
631
|
+
* restore the exact URL without relying on window.location.pathname (which
|
|
632
|
+
* could be the shell's route, not the remote's logical URL).
|
|
633
|
+
*/
|
|
634
|
+
_pushEmbedded(url) {
|
|
635
|
+
if (typeof history !== 'undefined') {
|
|
636
|
+
history.pushState({ flyRemoteUrl: url }, '', url);
|
|
637
|
+
}
|
|
638
|
+
this._url.set(url);
|
|
522
639
|
}
|
|
523
640
|
buildUrl(commands) {
|
|
524
641
|
const parts = commands
|
|
@@ -535,6 +652,226 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
535
652
|
args: [{ providedIn: 'root' }]
|
|
536
653
|
}], ctorParameters: () => [] });
|
|
537
654
|
|
|
655
|
+
/**
|
|
656
|
+
* fly-remote-styles — Shell-layer CSS loader for Native Federation remotes.
|
|
657
|
+
*
|
|
658
|
+
* Problem
|
|
659
|
+
* -------
|
|
660
|
+
* Federated remotes only ship JS chunks via Native Federation — their global
|
|
661
|
+
* `styles.css` (or its hashed equivalent) never loads in the shell, so any
|
|
662
|
+
* global CSS selector (e.g. `.circles-overlay`) silently breaks in embedded
|
|
663
|
+
* mode.
|
|
664
|
+
*
|
|
665
|
+
* Solution
|
|
666
|
+
* --------
|
|
667
|
+
* On first mount of each remote, the shell calls `loadRemoteStyles(appId,
|
|
668
|
+
* remoteBaseUrl)`. This function:
|
|
669
|
+
* 1. Derives the remote's `index.html` URL from `remoteBaseUrl`.
|
|
670
|
+
* 2. Fetches it (one network round-trip per remote, browser-cached thereafter).
|
|
671
|
+
* 3. Parses the HTML with DOMParser to extract the first
|
|
672
|
+
* `<link rel="stylesheet">` — the hashed production stylesheet
|
|
673
|
+
* (e.g. `styles-ABC123.css`). This is Strategy (b): index.html discovery.
|
|
674
|
+
* 4. Injects `<link rel="stylesheet" data-fly-app="<appId>">` into
|
|
675
|
+
* `document.head`. Idempotent: if an identical href is already present,
|
|
676
|
+
* the call is a no-op. If the href differs (hot upgrade), it is replaced.
|
|
677
|
+
*
|
|
678
|
+
* Why strategy (b)?
|
|
679
|
+
* -----------------
|
|
680
|
+
* (a) Unhashed `styles.css` — requires remote build config changes (out of scope).
|
|
681
|
+
* (b) Parse remote `index.html` — one tiny fetch per remote; browser caches it;
|
|
682
|
+
* works today without any remote changes.
|
|
683
|
+
* (c) `stylesUrl` in manifest/remoteEntry.json — cleanest long-term; requires
|
|
684
|
+
* protocol change to every remote's CI pipeline (out of scope for Wave A1).
|
|
685
|
+
*
|
|
686
|
+
* Unload decision
|
|
687
|
+
* ---------------
|
|
688
|
+
* `unloadRemoteStyles` is exported for symmetry but should NOT be called on
|
|
689
|
+
* normal window close. Keeping styles loaded across remounts avoids FOUC
|
|
690
|
+
* flicker when the user reopens the same app. The stylesheet is a few KB; the
|
|
691
|
+
* memory cost is negligible. Only call `unloadRemoteStyles` if you are certain
|
|
692
|
+
* the remote will never be opened again in this session (e.g. licence revoke).
|
|
693
|
+
*
|
|
694
|
+
* Caveats
|
|
695
|
+
* -------
|
|
696
|
+
* - CORS: the remote's dev/prod server must serve `index.html` with a
|
|
697
|
+
* permissive `Access-Control-Allow-Origin` header (or be same-origin via
|
|
698
|
+
* the YARP gateway). The fetch uses `credentials: 'omit'` to avoid
|
|
699
|
+
* credential-carrying preflights.
|
|
700
|
+
* - Race on rapid mount/unmount: if `loadRemoteStyles` is called a second time
|
|
701
|
+
* for the same appId while the first fetch is still in-flight, the second
|
|
702
|
+
* call joins the same in-flight Promise (idempotency guard at fetch level).
|
|
703
|
+
* - Angular hashing: Angular's production build hashes the stylesheet filename.
|
|
704
|
+
* DOMParser picks the first `<link rel="stylesheet">` in `<head>`, which is
|
|
705
|
+
* the single global stylesheet Angular emits. If a remote emits multiple
|
|
706
|
+
* stylesheets, only the first is loaded — acceptable for Wave A1.
|
|
707
|
+
*/
|
|
708
|
+
/** Per-appId cache of the resolved stylesheet href (or `null` if not found). */
|
|
709
|
+
const _resolvedHref = new Map();
|
|
710
|
+
/** In-flight fetch promises keyed by appId — prevents duplicate fetches. */
|
|
711
|
+
const _inFlight = new Map();
|
|
712
|
+
/**
|
|
713
|
+
* Discovers the hashed stylesheet href from the remote's `index.html`.
|
|
714
|
+
* Returns `null` if no stylesheet `<link>` is found or the fetch fails.
|
|
715
|
+
*/
|
|
716
|
+
async function _discoverStylesheetHref(remoteBaseUrl) {
|
|
717
|
+
const indexUrl = remoteBaseUrl.replace(/\/$/, '') + '/index.html';
|
|
718
|
+
try {
|
|
719
|
+
const res = await fetch(indexUrl, {
|
|
720
|
+
credentials: 'omit',
|
|
721
|
+
cache: 'default',
|
|
722
|
+
});
|
|
723
|
+
if (!res.ok)
|
|
724
|
+
return null;
|
|
725
|
+
const html = await res.text();
|
|
726
|
+
const doc = new DOMParser().parseFromString(html, 'text/html');
|
|
727
|
+
// Find the first stylesheet link in <head> — Angular emits exactly one.
|
|
728
|
+
const link = doc.head.querySelector('link[rel="stylesheet"]');
|
|
729
|
+
if (!link?.href)
|
|
730
|
+
return null;
|
|
731
|
+
// `link.href` from DOMParser is resolved relative to the parser's base,
|
|
732
|
+
// which defaults to `about:blank`. We get the raw `href` attribute instead.
|
|
733
|
+
const rawHref = link.getAttribute('href') ?? '';
|
|
734
|
+
if (!rawHref)
|
|
735
|
+
return null;
|
|
736
|
+
// If the remote emits an absolute URL, use it as-is; otherwise resolve
|
|
737
|
+
// against remoteBaseUrl.
|
|
738
|
+
if (rawHref.startsWith('http://') || rawHref.startsWith('https://') || rawHref.startsWith('//')) {
|
|
739
|
+
return rawHref;
|
|
740
|
+
}
|
|
741
|
+
return remoteBaseUrl.replace(/\/$/, '') + '/' + rawHref.replace(/^\//, '');
|
|
742
|
+
}
|
|
743
|
+
catch {
|
|
744
|
+
return null;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Injects the remote's stylesheet into `document.head`.
|
|
749
|
+
*
|
|
750
|
+
* @param appId - Stable identifier for the remote app (matches `DesktopApp.id`).
|
|
751
|
+
* @param remoteBaseUrl - Base URL of the remote, e.g. `https://circles.example.com`
|
|
752
|
+
* or `http://localhost:7202`. Must NOT include `/remoteEntry.json`.
|
|
753
|
+
*
|
|
754
|
+
* The call is idempotent:
|
|
755
|
+
* - If a `<link data-fly-app="appId">` with the same href already exists → no-op.
|
|
756
|
+
* - If the href differs (hot upgrade) → existing link is replaced.
|
|
757
|
+
* - If no stylesheet is found in `index.html` → no-op (logged as a warning).
|
|
758
|
+
*/
|
|
759
|
+
async function loadRemoteStyles(appId, remoteBaseUrl) {
|
|
760
|
+
if (typeof document === 'undefined')
|
|
761
|
+
return; // SSR guard
|
|
762
|
+
// Kick off or join an in-flight fetch for this appId.
|
|
763
|
+
let fetchPromise = _inFlight.get(appId);
|
|
764
|
+
if (!fetchPromise) {
|
|
765
|
+
if (_resolvedHref.has(appId)) {
|
|
766
|
+
// Already resolved in a previous call — skip the fetch.
|
|
767
|
+
fetchPromise = Promise.resolve(_resolvedHref.get(appId) ?? null);
|
|
768
|
+
}
|
|
769
|
+
else {
|
|
770
|
+
fetchPromise = _discoverStylesheetHref(remoteBaseUrl);
|
|
771
|
+
_inFlight.set(appId, fetchPromise);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
const href = await fetchPromise;
|
|
775
|
+
_inFlight.delete(appId);
|
|
776
|
+
_resolvedHref.set(appId, href);
|
|
777
|
+
if (!href) {
|
|
778
|
+
console.warn(`[FlyOS] loadRemoteStyles: no stylesheet found in ${remoteBaseUrl}/index.html for appId="${appId}"`);
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
const existing = document.head.querySelector(`link[data-fly-app="${CSS.escape(appId)}"]`);
|
|
782
|
+
if (existing) {
|
|
783
|
+
if (existing.getAttribute('href') === href)
|
|
784
|
+
return; // identical — no-op
|
|
785
|
+
// Hot upgrade: replace href.
|
|
786
|
+
existing.setAttribute('href', href);
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
const link = document.createElement('link');
|
|
790
|
+
link.rel = 'stylesheet';
|
|
791
|
+
link.setAttribute('data-fly-app', appId);
|
|
792
|
+
link.href = href;
|
|
793
|
+
document.head.appendChild(link);
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Removes the injected stylesheet for `appId` from `document.head`.
|
|
797
|
+
*
|
|
798
|
+
* NOTE: Prefer NOT calling this on normal window close — keeping styles loaded
|
|
799
|
+
* prevents FOUC flicker when the user reopens the same app. Call only if you
|
|
800
|
+
* are certain the remote will not be reopened in this session.
|
|
801
|
+
*/
|
|
802
|
+
function unloadRemoteStyles(appId) {
|
|
803
|
+
if (typeof document === 'undefined')
|
|
804
|
+
return; // SSR guard
|
|
805
|
+
const link = document.head.querySelector(`link[data-fly-app="${CSS.escape(appId)}"]`);
|
|
806
|
+
link?.parentNode?.removeChild(link);
|
|
807
|
+
_resolvedHref.delete(appId);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* Outlet for FlyOS-embedded routing. Renders the component associated with the
|
|
812
|
+
* first matching route in the consumer's `FLY_REMOTE_ROUTES` table, reacting to
|
|
813
|
+
* `FlyRemoteRouter.navigate(...)` calls.
|
|
814
|
+
*
|
|
815
|
+
* Use this **only in embedded mode**. Standalone remotes should keep their
|
|
816
|
+
* `<router-outlet>` — `FlyRemoteRouter.navigate` delegates to Angular's Router
|
|
817
|
+
* there, and this outlet would just shadow it.
|
|
818
|
+
*
|
|
819
|
+
* Recommended pattern in a remote's root component:
|
|
820
|
+
* ```html
|
|
821
|
+
* @if (router.isEmbedded) {
|
|
822
|
+
* <fly-remote-router-outlet />
|
|
823
|
+
* } @else {
|
|
824
|
+
* <router-outlet />
|
|
825
|
+
* }
|
|
826
|
+
* ```
|
|
827
|
+
*
|
|
828
|
+
* Why both? Standalone keeps full Angular routing — query params, guards,
|
|
829
|
+
* resolvers, lazy loading, RouterLink. Embedded gets a stripped-down
|
|
830
|
+
* synchronous outlet that matches against the same route table the remote
|
|
831
|
+
* declared via `FLY_REMOTE_ROUTES`. The same `router.navigate(['/foo', id])`
|
|
832
|
+
* call works in both modes; only the rendering surface differs.
|
|
833
|
+
*
|
|
834
|
+
* Limitations vs. `<router-outlet>`:
|
|
835
|
+
* - Synchronous components only (no `loadComponent` / `loadChildren`).
|
|
836
|
+
* - No guards / resolvers / data resolution.
|
|
837
|
+
* - No query-param / hash handling — only path segments.
|
|
838
|
+
* - No `RouterLink` directive — use `(click)="router.navigate(...)"`.
|
|
839
|
+
*
|
|
840
|
+
* Components rendered by this outlet read route params via `FlyRemoteRouter.params`:
|
|
841
|
+
* ```ts
|
|
842
|
+
* private readonly router = inject(FlyRemoteRouter);
|
|
843
|
+
* readonly id = computed(() => this.router.params()['id'] ?? '');
|
|
844
|
+
* ```
|
|
845
|
+
*/
|
|
846
|
+
class FlyRemoteRouterOutletComponent {
|
|
847
|
+
router = inject(FlyRemoteRouter);
|
|
848
|
+
/**
|
|
849
|
+
* Read directly from FlyRemoteRouter so the outlet re-renders whenever the
|
|
850
|
+
* URL changes (signal-based, OnPush-friendly).
|
|
851
|
+
*/
|
|
852
|
+
matched = this.router.matchedRoute;
|
|
853
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyRemoteRouterOutletComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
854
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: FlyRemoteRouterOutletComponent, isStandalone: true, selector: "fly-remote-router-outlet", ngImport: i0, template: `
|
|
855
|
+
@if (matched(); as m) {
|
|
856
|
+
<ng-container *ngComponentOutlet="m.route.component" />
|
|
857
|
+
}
|
|
858
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
859
|
+
}
|
|
860
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyRemoteRouterOutletComponent, decorators: [{
|
|
861
|
+
type: Component,
|
|
862
|
+
args: [{
|
|
863
|
+
selector: 'fly-remote-router-outlet',
|
|
864
|
+
standalone: true,
|
|
865
|
+
imports: [NgComponentOutlet],
|
|
866
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
867
|
+
template: `
|
|
868
|
+
@if (matched(); as m) {
|
|
869
|
+
<ng-container *ngComponentOutlet="m.route.component" />
|
|
870
|
+
}
|
|
871
|
+
`,
|
|
872
|
+
}]
|
|
873
|
+
}] });
|
|
874
|
+
|
|
538
875
|
/**
|
|
539
876
|
* Translates a key using the shared I18nService.
|
|
540
877
|
*
|
|
@@ -3882,5 +4219,5 @@ const AUDIENCE_ERROR_CODES = {
|
|
|
3882
4219
|
* Generated bundle index. Do not edit.
|
|
3883
4220
|
*/
|
|
3884
4221
|
|
|
3885
|
-
export { AGENT_DRAG_MIME, AGENT_PAYLOAD_VERSION, AUDIENCE_ERROR_CODES, AUDIENCE_LIMITS, AUDIENCE_PRESETS, AUDIENCE_TERM_KINDS, AgentCommandRegistry, AgentDropRegistry, AgentPayloadOversizeError, AudienceBuilderComponent, AuthService, ContextMenuComponent, DEFAULT_AGENT_PAYLOAD_LIMITS, DEFAULT_FLY_THEME_MODE, DialogResult, FLY_LOCALE_CATALOG, FLY_THEME_MODE_IDS, FlyAgentDraggableDirective, FlyBlockUiComponent, FlyFileUploadComponent, FlyImageUploadComponent, FlyRemoteRouter, FlyThemeService, I18nService, LAUNCH_CONTEXT, MessageBoxButtons, MessageBoxComponent, MessageBoxIcon, MessageBoxService, MockAuthService, RTL_LOCALE_SET, SHARE_ORG_CHART_SYSTEM_KEY_APPS, SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT, SHARE_PANEL_DEFAULT_FILE_LEVELS, SharePanelComponent, StandaloneWindowManagerService, TranslatePipe, WINDOW_DATA, WindowManagerService, findLocaleByDialect, findLocaleByPrefix, isRtlLocale, isRtlLocaleEntry, normalizeFlyTheme, trimAgentPayload, trimAgentString, utf8ByteLength, validateAgentPayload };
|
|
4222
|
+
export { AGENT_DRAG_MIME, AGENT_PAYLOAD_VERSION, AUDIENCE_ERROR_CODES, AUDIENCE_LIMITS, AUDIENCE_PRESETS, AUDIENCE_TERM_KINDS, AgentCommandRegistry, AgentDropRegistry, AgentPayloadOversizeError, AudienceBuilderComponent, AuthService, ContextMenuComponent, DEFAULT_AGENT_PAYLOAD_LIMITS, DEFAULT_FLY_THEME_MODE, DialogResult, FLY_LOCALE_CATALOG, FLY_REMOTE_ROUTES, FLY_THEME_MODE_IDS, FlyAgentDraggableDirective, FlyBlockUiComponent, FlyFileUploadComponent, FlyImageUploadComponent, FlyRemoteRouter, FlyRemoteRouterOutletComponent, FlyThemeService, I18nService, LAUNCH_CONTEXT, MessageBoxButtons, MessageBoxComponent, MessageBoxIcon, MessageBoxService, MockAuthService, RTL_LOCALE_SET, SHARE_ORG_CHART_SYSTEM_KEY_APPS, SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT, SHARE_PANEL_DEFAULT_FILE_LEVELS, SharePanelComponent, StandaloneWindowManagerService, TranslatePipe, WINDOW_DATA, WindowManagerService, findLocaleByDialect, findLocaleByPrefix, isRtlLocale, isRtlLocaleEntry, loadRemoteStyles, matchFlyRoutePattern, normalizeFlyTheme, trimAgentPayload, trimAgentString, unloadRemoteStyles, utf8ByteLength, validateAgentPayload };
|
|
3886
4223
|
//# sourceMappingURL=mohamedatia-fly-design-system.mjs.map
|