@flyos/design-system 1.6.0 → 1.7.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.
|
@@ -48,7 +48,7 @@ import { CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
|
|
|
48
48
|
// tools/publish-library.ps1 at bump time, and asserted by the spec beside this file.
|
|
49
49
|
// Used only for the diagnostic message; the duplicate-instance detection itself is
|
|
50
50
|
// version-agnostic, so a stale literal misnames a fork rather than hiding one.
|
|
51
|
-
const FLY_DS_VERSION = '1.
|
|
51
|
+
const FLY_DS_VERSION = '1.7.0';
|
|
52
52
|
const FLY_DS_REGISTRY_KEY = '__FLY_DS_INSTANCES__';
|
|
53
53
|
/**
|
|
54
54
|
* Records this design-system instance on the shared `scope` and returns the
|
|
@@ -4768,6 +4768,10 @@ function unloadRemoteStyles(appId) {
|
|
|
4768
4768
|
_inFlight.delete(appId);
|
|
4769
4769
|
}
|
|
4770
4770
|
|
|
4771
|
+
/** Accepts a bare class or a `{ default }` module namespace, like Angular's `loadComponent`. */
|
|
4772
|
+
function unwrapLoaded(loaded) {
|
|
4773
|
+
return typeof loaded === 'function' ? loaded : loaded.default;
|
|
4774
|
+
}
|
|
4771
4775
|
/**
|
|
4772
4776
|
* Outlet for FlyOS-embedded routing. Renders the component associated with the
|
|
4773
4777
|
* first matching route in the consumer's `FLY_REMOTE_ROUTES` table, reacting to
|
|
@@ -4787,13 +4791,13 @@ function unloadRemoteStyles(appId) {
|
|
|
4787
4791
|
* ```
|
|
4788
4792
|
*
|
|
4789
4793
|
* Why both? Standalone keeps full Angular routing — query params, guards,
|
|
4790
|
-
* resolvers,
|
|
4791
|
-
*
|
|
4792
|
-
*
|
|
4793
|
-
*
|
|
4794
|
+
* resolvers, RouterLink. Embedded gets a stripped-down outlet that matches
|
|
4795
|
+
* against the same route table the remote declared via `FLY_REMOTE_ROUTES`. The
|
|
4796
|
+
* same `router.navigate(['/foo', id])` call works in both modes; only the
|
|
4797
|
+
* rendering surface differs.
|
|
4794
4798
|
*
|
|
4795
4799
|
* Limitations vs. `<router-outlet>`:
|
|
4796
|
-
* -
|
|
4800
|
+
* - No `loadChildren` (route-level code splitting only — see `loadComponent`).
|
|
4797
4801
|
* - No guards / resolvers / data resolution.
|
|
4798
4802
|
* - No query-param / hash handling — only path segments.
|
|
4799
4803
|
* - No `RouterLink` directive — use `(click)="router.navigate(...)"`.
|
|
@@ -4803,19 +4807,97 @@ function unloadRemoteStyles(appId) {
|
|
|
4803
4807
|
* private readonly router = inject(FlyRemoteRouter);
|
|
4804
4808
|
* readonly id = computed(() => this.router.params()['id'] ?? '');
|
|
4805
4809
|
* ```
|
|
4810
|
+
*
|
|
4811
|
+
* ## Lazy routes
|
|
4812
|
+
*
|
|
4813
|
+
* A route may supply `loadComponent: () => import('./x').then(m => m.XComponent)`
|
|
4814
|
+
* instead of `component:`. The chunk is fetched on first match and cached for the
|
|
4815
|
+
* lifetime of the outlet, so revisiting a route is synchronous.
|
|
4816
|
+
*
|
|
4817
|
+
* Three behaviours are deliberate, and each exists because the obvious
|
|
4818
|
+
* alternative is worse:
|
|
4819
|
+
*
|
|
4820
|
+
* - **The previous component stays mounted while the next one loads.** Blanking
|
|
4821
|
+
* the outlet first would flash empty on every navigation, and a remote's chunks
|
|
4822
|
+
* are served from its own origin — usually a few milliseconds. This matches
|
|
4823
|
+
* Angular's Router, which does not tear down the current route until the next
|
|
4824
|
+
* one is ready.
|
|
4825
|
+
* - **A stale resolution never wins.** Navigating A → B → C while B is still in
|
|
4826
|
+
* flight must not render B over C. Each resolution carries a sequence token and
|
|
4827
|
+
* is discarded unless it is still the latest.
|
|
4828
|
+
* - **A failed load is reported to `ErrorHandler`, not swallowed.** The
|
|
4829
|
+
* chunk-404-after-redeploy self-heal (`provideFlyChunkReloadRecovery`) lives in
|
|
4830
|
+
* the root `ErrorHandler`, and a rejected promise inside an effect would reach
|
|
4831
|
+
* `unhandledrejection` instead — where that handler never sees it. Routing it
|
|
4832
|
+
* here is what lets a stale embedded remote recover. The failed loader is
|
|
4833
|
+
* evicted so a later attempt genuinely retries rather than reusing the
|
|
4834
|
+
* rejected promise.
|
|
4806
4835
|
*/
|
|
4807
4836
|
class FlyRemoteRouterOutletComponent {
|
|
4808
4837
|
router = inject(FlyRemoteRouter);
|
|
4838
|
+
errorHandler = inject(ErrorHandler);
|
|
4809
4839
|
/**
|
|
4810
4840
|
* Read directly from FlyRemoteRouter so the outlet re-renders whenever the
|
|
4811
4841
|
* URL changes (signal-based, OnPush-friendly).
|
|
4812
4842
|
*/
|
|
4813
4843
|
matched = this.router.matchedRoute;
|
|
4844
|
+
/** Loaders that have resolved — consulted synchronously, so a revisit never flashes. */
|
|
4845
|
+
loaded = new Map();
|
|
4846
|
+
/** Loaders currently in flight, so a param change mid-load doesn't start a second fetch. */
|
|
4847
|
+
inFlight = new Map();
|
|
4848
|
+
/** Bumped per resolution attempt; a late `.then` compares against it and bows out. */
|
|
4849
|
+
seq = 0;
|
|
4850
|
+
component = signal(null, ...(ngDevMode ? [{ debugName: "component" }] : /* istanbul ignore next */ []));
|
|
4851
|
+
/** The component class currently projected into `*ngComponentOutlet`. */
|
|
4852
|
+
rendered = this.component.asReadonly();
|
|
4853
|
+
constructor() {
|
|
4854
|
+
effect(() => {
|
|
4855
|
+
const route = this.matched()?.route ?? null;
|
|
4856
|
+
const token = ++this.seq;
|
|
4857
|
+
if (!route) {
|
|
4858
|
+
this.component.set(null);
|
|
4859
|
+
return;
|
|
4860
|
+
}
|
|
4861
|
+
if (route.component) {
|
|
4862
|
+
this.component.set(route.component);
|
|
4863
|
+
return;
|
|
4864
|
+
}
|
|
4865
|
+
const loader = route.loadComponent;
|
|
4866
|
+
const already = this.loaded.get(loader);
|
|
4867
|
+
if (already) {
|
|
4868
|
+
// Synchronous: no microtask gap, so re-matching the same lazy route on a
|
|
4869
|
+
// param change cannot blank or re-create the component.
|
|
4870
|
+
this.component.set(already);
|
|
4871
|
+
return;
|
|
4872
|
+
}
|
|
4873
|
+
let pending = this.inFlight.get(loader);
|
|
4874
|
+
if (!pending) {
|
|
4875
|
+
pending = Promise.resolve(loader()).then(unwrapLoaded);
|
|
4876
|
+
this.inFlight.set(loader, pending);
|
|
4877
|
+
}
|
|
4878
|
+
pending
|
|
4879
|
+
.then((cmp) => {
|
|
4880
|
+
this.loaded.set(loader, cmp);
|
|
4881
|
+
this.inFlight.delete(loader);
|
|
4882
|
+
if (token === this.seq)
|
|
4883
|
+
this.component.set(cmp);
|
|
4884
|
+
})
|
|
4885
|
+
.catch((err) => {
|
|
4886
|
+
// Evict so a retry re-runs the import instead of re-awaiting a rejection.
|
|
4887
|
+
this.inFlight.delete(loader);
|
|
4888
|
+
// Leave the previous component mounted — a blank outlet tells the user
|
|
4889
|
+
// nothing, and Angular's Router likewise keeps the current route on a
|
|
4890
|
+
// failed lazy load.
|
|
4891
|
+
if (token === this.seq)
|
|
4892
|
+
this.errorHandler.handleError(err);
|
|
4893
|
+
});
|
|
4894
|
+
});
|
|
4895
|
+
}
|
|
4814
4896
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyRemoteRouterOutletComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4815
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyRemoteRouterOutletComponent, isStandalone: true, selector: "fly-remote-router-outlet", ngImport: i0, template: `
|
|
4816
|
-
@if (
|
|
4817
|
-
<ng-container *ngComponentOutlet="
|
|
4818
|
-
}
|
|
4897
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyRemoteRouterOutletComponent, isStandalone: true, selector: "fly-remote-router-outlet", ngImport: i0, template: `
|
|
4898
|
+
@if (rendered(); as cmp) {
|
|
4899
|
+
<ng-container *ngComponentOutlet="cmp" />
|
|
4900
|
+
}
|
|
4819
4901
|
`, isInline: true, dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4820
4902
|
}
|
|
4821
4903
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyRemoteRouterOutletComponent, decorators: [{
|
|
@@ -4825,13 +4907,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
|
|
|
4825
4907
|
standalone: true,
|
|
4826
4908
|
imports: [NgComponentOutlet],
|
|
4827
4909
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
4828
|
-
template: `
|
|
4829
|
-
@if (
|
|
4830
|
-
<ng-container *ngComponentOutlet="
|
|
4831
|
-
}
|
|
4910
|
+
template: `
|
|
4911
|
+
@if (rendered(); as cmp) {
|
|
4912
|
+
<ng-container *ngComponentOutlet="cmp" />
|
|
4913
|
+
}
|
|
4832
4914
|
`,
|
|
4833
4915
|
}]
|
|
4834
|
-
}] });
|
|
4916
|
+
}], ctorParameters: () => [] });
|
|
4835
4917
|
|
|
4836
4918
|
/**
|
|
4837
4919
|
* Shared mock AuthService base class for Business App developers.
|