@mohamedatia/fly-design-system 2.7.1 → 2.7.3
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/fesm2022/mohamedatia-fly-design-system.mjs +172 -22
- package/fesm2022/mohamedatia-fly-design-system.mjs.map +1 -1
- package/package.json +1 -1
- package/scss/_business-app-buttons.scss +4 -0
- package/scss/_theme-auto.scss +2 -0
- package/scss/_theme-dark.scss +2 -0
- package/scss/_theme-light.scss +2 -0
- package/scss/_theme-spatial.scss +2 -0
- package/scss/_tokens.scss +2 -0
- package/types/mohamedatia-fly-design-system.d.ts +102 -7
- package/types/mohamedatia-fly-design-system.d.ts.map +1 -1
- package/scss/apps.scss +0 -886
|
@@ -395,6 +395,40 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
395
395
|
* routing.
|
|
396
396
|
*/
|
|
397
397
|
const FLY_REMOTE_ROUTES = new InjectionToken('FLY_REMOTE_ROUTES');
|
|
398
|
+
/**
|
|
399
|
+
* Optional injection token a remote provides at its root to declare the shell
|
|
400
|
+
* mount path prefix (e.g. `'/desktop'`). When set, `FlyRemoteRouter.segments()`
|
|
401
|
+
* strips this prefix from `window.location.pathname` before matching routes.
|
|
402
|
+
*
|
|
403
|
+
* **Why this is needed:** In embedded mode the remote is loaded via
|
|
404
|
+
* `NgComponentOutlet` inside the shell SPA. The shell's own Angular route may
|
|
405
|
+
* leave `window.location.pathname` as `/desktop` (or any shell-level path).
|
|
406
|
+
* Without stripping that prefix, `segments()` returns `['desktop']` which
|
|
407
|
+
* matches none of the remote's Circles-internal routes (e.g. `''`, `'signals'`,
|
|
408
|
+
* `'signals/:id'`), so `<fly-remote-router-outlet>` renders nothing.
|
|
409
|
+
*
|
|
410
|
+
* **Default behaviour (token absent):** `FlyRemoteRouter` initialises `_url`
|
|
411
|
+
* to `'/'` in embedded mode, completely ignoring `window.location.pathname`.
|
|
412
|
+
* This is safe because the shell does not push the remote's logical URL into
|
|
413
|
+
* browser history — only `navigate()` / `navigateByUrl()` calls do.
|
|
414
|
+
*
|
|
415
|
+
* **Usage in a remote root component:**
|
|
416
|
+
* ```ts
|
|
417
|
+
* providers: [
|
|
418
|
+
* FlyRemoteRouter,
|
|
419
|
+
* { provide: FLY_REMOTE_ROUTES, useValue: MY_ROUTES },
|
|
420
|
+
* { provide: FLY_REMOTE_BASE_PATH, useValue: '/desktop' },
|
|
421
|
+
* ]
|
|
422
|
+
* ```
|
|
423
|
+
*
|
|
424
|
+
* With `basePath = '/desktop'` and `window.location.pathname = '/desktop'`,
|
|
425
|
+
* the stripped URL is `'/'` → `segments() = []` → matches the `''` default
|
|
426
|
+
* route. With pathname `'/desktop/signals/abc'`, segments become
|
|
427
|
+
* `['signals', 'abc']` → matches `'signals/:id'`.
|
|
428
|
+
*
|
|
429
|
+
* Available since design-system **2.7.2**.
|
|
430
|
+
*/
|
|
431
|
+
const FLY_REMOTE_BASE_PATH = new InjectionToken('FLY_REMOTE_BASE_PATH');
|
|
398
432
|
/**
|
|
399
433
|
* Match a route's path pattern against URL segments. Returns the captured params
|
|
400
434
|
* map on a successful match, or `null` if the pattern can't match. Empty path
|
|
@@ -489,6 +523,15 @@ class FlyRemoteRouter {
|
|
|
489
523
|
* driving its own switch/template — `matchedRoute` stays `null`.
|
|
490
524
|
*/
|
|
491
525
|
routes = inject(FLY_REMOTE_ROUTES, { optional: true }) ?? [];
|
|
526
|
+
/**
|
|
527
|
+
* Shell mount path prefix to strip from `window.location.pathname` when
|
|
528
|
+
* deriving the initial URL in embedded mode. See `FLY_REMOTE_BASE_PATH` docs.
|
|
529
|
+
*
|
|
530
|
+
* When absent (the common case), the embedded router initialises `_url` to
|
|
531
|
+
* `'/'` and ignores `window.location.pathname` entirely — correct because the
|
|
532
|
+
* shell SPA never encodes the remote's logical route into the browser URL.
|
|
533
|
+
*/
|
|
534
|
+
basePath = inject(FLY_REMOTE_BASE_PATH, { optional: true }) ?? null;
|
|
492
535
|
/**
|
|
493
536
|
* True when this remote is rendered inside the FlyOS desktop shell.
|
|
494
537
|
*
|
|
@@ -565,9 +608,16 @@ class FlyRemoteRouter {
|
|
|
565
608
|
});
|
|
566
609
|
}
|
|
567
610
|
else if (this.isEmbedded) {
|
|
568
|
-
// Embedded:
|
|
611
|
+
// Embedded: the shell SPA keeps its own route in window.location.pathname
|
|
612
|
+
// (e.g. '/desktop') which is meaningless for the remote's route table.
|
|
613
|
+
// Default: initialise to '/' so the remote's empty-path default route
|
|
614
|
+
// matches on first render. When FLY_REMOTE_BASE_PATH is provided, strip
|
|
615
|
+
// it from pathname first — useful if the shell does push a remote-aware
|
|
616
|
+
// URL (e.g. '/desktop/signals/abc' with basePath '/desktop').
|
|
569
617
|
if (typeof window !== 'undefined') {
|
|
570
|
-
|
|
618
|
+
const rawPath = window.location.pathname || '/';
|
|
619
|
+
const initialUrl = this._resolveEmbeddedInitialUrl(rawPath);
|
|
620
|
+
this._url.set(initialUrl);
|
|
571
621
|
// Listen for browser back/forward so segments() stays in sync with
|
|
572
622
|
// history entries pushed by navigate() / navigateByUrl().
|
|
573
623
|
const onPopState = (event) => {
|
|
@@ -623,6 +673,34 @@ class FlyRemoteRouter {
|
|
|
623
673
|
history.back();
|
|
624
674
|
}
|
|
625
675
|
}
|
|
676
|
+
/**
|
|
677
|
+
* Resolve the logical URL to seed `_url` from on embedded initial load.
|
|
678
|
+
*
|
|
679
|
+
* - No basePath: always return `'/'`. The shell's SPA path (e.g. `/desktop`)
|
|
680
|
+
* is irrelevant to the remote — `navigate()` is the only thing that should
|
|
681
|
+
* move the remote's URL.
|
|
682
|
+
* - basePath provided: strip it from `rawPath`. If rawPath starts with the
|
|
683
|
+
* basePath, the remainder becomes the seed URL (normalised to start with
|
|
684
|
+
* `'/'`). If it doesn't match (e.g. shell changed its own route), fall back
|
|
685
|
+
* to `'/'` rather than surfacing shell-specific segments to the remote's
|
|
686
|
+
* route table.
|
|
687
|
+
*/
|
|
688
|
+
_resolveEmbeddedInitialUrl(rawPath) {
|
|
689
|
+
if (!this.basePath) {
|
|
690
|
+
// Default: ignore the shell's pathname, start at remote root.
|
|
691
|
+
return '/';
|
|
692
|
+
}
|
|
693
|
+
const base = this.basePath.endsWith('/') ? this.basePath.slice(0, -1) : this.basePath;
|
|
694
|
+
if (rawPath === base || rawPath === base + '/') {
|
|
695
|
+
return '/';
|
|
696
|
+
}
|
|
697
|
+
if (rawPath.startsWith(base + '/')) {
|
|
698
|
+
const remainder = rawPath.slice(base.length);
|
|
699
|
+
return remainder.startsWith('/') ? remainder : '/' + remainder;
|
|
700
|
+
}
|
|
701
|
+
// Shell path doesn't begin with our basePath — fall through to root.
|
|
702
|
+
return '/';
|
|
703
|
+
}
|
|
626
704
|
/**
|
|
627
705
|
* In embedded mode: push a real browser history entry (so back/forward work)
|
|
628
706
|
* and update the internal signal synchronously.
|
|
@@ -671,9 +749,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
671
749
|
* 3. Parses the HTML with DOMParser to extract the first
|
|
672
750
|
* `<link rel="stylesheet">` — the hashed production stylesheet
|
|
673
751
|
* (e.g. `styles-ABC123.css`). This is Strategy (b): index.html discovery.
|
|
674
|
-
* 4. Injects `<
|
|
675
|
-
*
|
|
676
|
-
*
|
|
752
|
+
* 4. Injects an inline `<style data-fly-app="<appId>">` block containing
|
|
753
|
+
* `@layer remote { @import url("..."); }` into `document.head`.
|
|
754
|
+
* Idempotent: if a `<style data-fly-app="appId">` with the same href
|
|
755
|
+
* is already present, the call is a no-op. If the href differs (hot
|
|
756
|
+
* upgrade), the existing element is replaced.
|
|
677
757
|
*
|
|
678
758
|
* Why strategy (b)?
|
|
679
759
|
* -----------------
|
|
@@ -683,6 +763,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
683
763
|
* (c) `stylesUrl` in manifest/remoteEntry.json — cleanest long-term; requires
|
|
684
764
|
* protocol change to every remote's CI pipeline (out of scope for Wave A1).
|
|
685
765
|
*
|
|
766
|
+
* CSS Cascade Layer — Option A (`@import` inside inline `<style>`)
|
|
767
|
+
* ----------------------------------------------------------------
|
|
768
|
+
* Remote stylesheets are injected inside `@layer remote { @import url("..."); }`
|
|
769
|
+
* rather than as bare `<link rel="stylesheet">`. This keeps remote styles in a
|
|
770
|
+
* named cascade layer so the shell can reliably override them via `@layer overrides`.
|
|
771
|
+
*
|
|
772
|
+
* Option A was chosen over:
|
|
773
|
+
* - Option B (fetch+inline): relative `url(...)` paths inside the remote CSS
|
|
774
|
+
* would resolve against the shell origin instead of the remote origin,
|
|
775
|
+
* breaking fonts and images. Dealbreaker without a full URL-rewrite pass.
|
|
776
|
+
* - Option C (`<link layer="remote">`): the `layer` attribute on `<link>` is
|
|
777
|
+
* not yet shipped in Firefox (as of 2025-05). Chromium 99+ and Safari 16.4+
|
|
778
|
+
* support it, but Firefox is still behind, making it non-portable today.
|
|
779
|
+
*
|
|
780
|
+
* Trade-off of Option A: `@import` inside `@layer` delays style application until
|
|
781
|
+
* the remote stylesheet is fetched (same as a plain `<link>`, effectively). Modern
|
|
782
|
+
* engines schedule it as a discoverable resource from the inline `<style>` element.
|
|
783
|
+
* The performance characteristic is equivalent to the old `<link>` approach.
|
|
784
|
+
*
|
|
785
|
+
* Layer order declaration
|
|
786
|
+
* -----------------------
|
|
787
|
+
* A one-time `<style data-fly-layers>` element is prepended to `<head>` the first
|
|
788
|
+
* time any remote style is loaded. It establishes the canonical layer order:
|
|
789
|
+
* reset → designsystem → shell → remote → overrides
|
|
790
|
+
* This guarantees that even if individual `@layer` blocks are injected in any
|
|
791
|
+
* order at runtime, the cascade priority is always deterministic.
|
|
792
|
+
*
|
|
686
793
|
* Unload decision
|
|
687
794
|
* ---------------
|
|
688
795
|
* `unloadRemoteStyles` is exported for symmetry but should NOT be called on
|
|
@@ -696,7 +803,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
696
803
|
* - CORS: the remote's dev/prod server must serve `index.html` with a
|
|
697
804
|
* permissive `Access-Control-Allow-Origin` header (or be same-origin via
|
|
698
805
|
* the YARP gateway). The fetch uses `credentials: 'omit'` to avoid
|
|
699
|
-
* credential-carrying preflights.
|
|
806
|
+
* credential-carrying preflights. The CSS file itself must also be CORS-
|
|
807
|
+
* accessible since `@import` inside a `<style>` is subject to CORS checks.
|
|
700
808
|
* - Race on rapid mount/unmount: if `loadRemoteStyles` is called a second time
|
|
701
809
|
* for the same appId while the first fetch is still in-flight, the second
|
|
702
810
|
* call joins the same in-flight Promise (idempotency guard at fetch level).
|
|
@@ -709,6 +817,35 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
709
817
|
const _resolvedHref = new Map();
|
|
710
818
|
/** In-flight fetch promises keyed by appId — prevents duplicate fetches. */
|
|
711
819
|
const _inFlight = new Map();
|
|
820
|
+
/**
|
|
821
|
+
* Whether the canonical layer-order declaration has already been injected.
|
|
822
|
+
* Module-level so it survives across multiple `loadRemoteStyles` calls within
|
|
823
|
+
* the same shell session but is reset on a full page reload.
|
|
824
|
+
*/
|
|
825
|
+
let _layersDeclared = false;
|
|
826
|
+
/**
|
|
827
|
+
* Injects a single `<style data-fly-layers>` element at the top of `<head>`
|
|
828
|
+
* that establishes the canonical cascade-layer priority order for the shell:
|
|
829
|
+
*
|
|
830
|
+
* reset → designsystem → shell → remote → overrides
|
|
831
|
+
*
|
|
832
|
+
* Any subsequent `@layer X { … }` block lands in the already-established slot
|
|
833
|
+
* regardless of injection order, so late-arriving remote stylesheets never
|
|
834
|
+
* "win" over shell or override rules. Idempotent — runs at most once per page.
|
|
835
|
+
*/
|
|
836
|
+
function _ensureLayerOrder() {
|
|
837
|
+
if (_layersDeclared)
|
|
838
|
+
return;
|
|
839
|
+
_layersDeclared = true;
|
|
840
|
+
// Guard against duplicate elements in the DOM (e.g. hot-module-reload edge cases).
|
|
841
|
+
if (document.head.querySelector('style[data-fly-layers]'))
|
|
842
|
+
return;
|
|
843
|
+
const style = document.createElement('style');
|
|
844
|
+
style.setAttribute('data-fly-layers', '');
|
|
845
|
+
style.textContent = '@layer reset, designsystem, shell, remote, overrides;';
|
|
846
|
+
// Prepend so this always precedes any other layer-bearing <style> blocks.
|
|
847
|
+
document.head.insertBefore(style, document.head.firstChild);
|
|
848
|
+
}
|
|
712
849
|
/**
|
|
713
850
|
* Discovers the hashed stylesheet href from the remote's `index.html`.
|
|
714
851
|
* Returns `null` if no stylesheet `<link>` is found or the fetch fails.
|
|
@@ -752,13 +889,24 @@ async function _discoverStylesheetHref(remoteBaseUrl) {
|
|
|
752
889
|
* or `http://localhost:7202`. Must NOT include `/remoteEntry.json`.
|
|
753
890
|
*
|
|
754
891
|
* The call is idempotent:
|
|
755
|
-
* - If a `<
|
|
756
|
-
*
|
|
892
|
+
* - If a `<style data-fly-app="appId">` whose content references the same href
|
|
893
|
+
* already exists → no-op.
|
|
894
|
+
* - If the href differs (hot upgrade) → existing element is replaced.
|
|
757
895
|
* - If no stylesheet is found in `index.html` → no-op (logged as a warning).
|
|
896
|
+
*
|
|
897
|
+
* Injection shape:
|
|
898
|
+
* <style data-fly-app="<appId>" data-fly-href="<href>">
|
|
899
|
+
* @layer remote { @import url("<href>"); }
|
|
900
|
+
* </style>
|
|
901
|
+
*
|
|
902
|
+
* The `data-fly-href` attribute stores the discovered href separately from the
|
|
903
|
+
* style content so idempotency checks can compare the URL without parsing CSS.
|
|
758
904
|
*/
|
|
759
905
|
async function loadRemoteStyles(appId, remoteBaseUrl) {
|
|
760
906
|
if (typeof document === 'undefined')
|
|
761
907
|
return; // SSR guard
|
|
908
|
+
// Ensure the canonical layer order is declared before any remote layer is injected.
|
|
909
|
+
_ensureLayerOrder();
|
|
762
910
|
// Kick off or join an in-flight fetch for this appId.
|
|
763
911
|
let fetchPromise = _inFlight.get(appId);
|
|
764
912
|
if (!fetchPromise) {
|
|
@@ -778,19 +926,21 @@ async function loadRemoteStyles(appId, remoteBaseUrl) {
|
|
|
778
926
|
console.warn(`[FlyOS] loadRemoteStyles: no stylesheet found in ${remoteBaseUrl}/index.html for appId="${appId}"`);
|
|
779
927
|
return;
|
|
780
928
|
}
|
|
781
|
-
const
|
|
929
|
+
const selector = `style[data-fly-app="${CSS.escape(appId)}"]`;
|
|
930
|
+
const existing = document.head.querySelector(selector);
|
|
782
931
|
if (existing) {
|
|
783
|
-
if (existing.getAttribute('href') === href)
|
|
932
|
+
if (existing.getAttribute('data-fly-href') === href)
|
|
784
933
|
return; // identical — no-op
|
|
785
|
-
// Hot upgrade: replace
|
|
786
|
-
existing.
|
|
787
|
-
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
934
|
+
// Hot upgrade: replace the entire element so the @import URL updates atomically.
|
|
935
|
+
existing.remove();
|
|
936
|
+
}
|
|
937
|
+
// Option A: inline <style> with @layer remote { @import url("..."); }.
|
|
938
|
+
// See module JSDoc for the rationale over Options B and C.
|
|
939
|
+
const style = document.createElement('style');
|
|
940
|
+
style.setAttribute('data-fly-app', appId);
|
|
941
|
+
style.setAttribute('data-fly-href', href);
|
|
942
|
+
style.textContent = `@layer remote { @import url("${href}"); }`;
|
|
943
|
+
document.head.appendChild(style);
|
|
794
944
|
}
|
|
795
945
|
/**
|
|
796
946
|
* Removes the injected stylesheet for `appId` from `document.head`.
|
|
@@ -802,8 +952,8 @@ async function loadRemoteStyles(appId, remoteBaseUrl) {
|
|
|
802
952
|
function unloadRemoteStyles(appId) {
|
|
803
953
|
if (typeof document === 'undefined')
|
|
804
954
|
return; // SSR guard
|
|
805
|
-
const
|
|
806
|
-
|
|
955
|
+
const style = document.head.querySelector(`style[data-fly-app="${CSS.escape(appId)}"]`);
|
|
956
|
+
style?.parentNode?.removeChild(style);
|
|
807
957
|
_resolvedHref.delete(appId);
|
|
808
958
|
}
|
|
809
959
|
|
|
@@ -4219,5 +4369,5 @@ const AUDIENCE_ERROR_CODES = {
|
|
|
4219
4369
|
* Generated bundle index. Do not edit.
|
|
4220
4370
|
*/
|
|
4221
4371
|
|
|
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 };
|
|
4372
|
+
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_BASE_PATH, 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 };
|
|
4223
4373
|
//# sourceMappingURL=mohamedatia-fly-design-system.mjs.map
|