@mohamedatia/fly-design-system 2.7.3 → 2.8.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.
@@ -296,7 +296,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
296
296
  }] });
297
297
 
298
298
  /** Single source of truth for persisted / API theme strings. */
299
- const FLY_THEME_MODE_IDS = ['light', 'spatial', 'dark'];
299
+ const FLY_THEME_MODE_IDS = ['light', 'dark'];
300
300
  /** Factory default: `dark` (opaque dark / `html.dark-theme`); shell i18n `settings.theme.dark`. Keep in sync with `UserSettings` defaults. */
301
301
  const DEFAULT_FLY_THEME_MODE = 'dark';
302
302
  /** Coerce unknown values (localStorage, API) to a valid theme mode. */
@@ -307,7 +307,7 @@ function normalizeFlyTheme(value) {
307
307
  return DEFAULT_FLY_THEME_MODE;
308
308
  }
309
309
  /**
310
- * Applies `html.light-theme` / `html.spatial-theme` / `html.dark-theme` for DS SCSS.
310
+ * Applies `html.light-theme` / `html.dark-theme` for DS SCSS.
311
311
  * Shell and standalone Business Apps use the same service (federation singleton when shared).
312
312
  */
313
313
  class FlyThemeService {
@@ -319,11 +319,8 @@ class FlyThemeService {
319
319
  return;
320
320
  }
321
321
  const html = document.documentElement;
322
- html.classList.remove('light-theme', 'spatial-theme', 'dark-theme');
323
- if (mode === 'spatial') {
324
- html.classList.add('spatial-theme');
325
- }
326
- else if (mode === 'dark') {
322
+ html.classList.remove('light-theme', 'dark-theme');
323
+ if (mode === 'dark') {
327
324
  html.classList.add('dark-theme');
328
325
  }
329
326
  else {
@@ -745,15 +742,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
745
742
  * On first mount of each remote, the shell calls `loadRemoteStyles(appId,
746
743
  * remoteBaseUrl)`. This function:
747
744
  * 1. Derives the remote's `index.html` URL from `remoteBaseUrl`.
748
- * 2. Fetches it (one network round-trip per remote, browser-cached thereafter).
745
+ * 2. Fetches it (one network round-trip per remote; browser-cached thereafter).
749
746
  * 3. Parses the HTML with DOMParser to extract the first
750
747
  * `<link rel="stylesheet">` — the hashed production stylesheet
751
748
  * (e.g. `styles-ABC123.css`). This is Strategy (b): index.html discovery.
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.
749
+ * 4. Injects the stylesheet into `document.head` using one of two approaches
750
+ * (see Injection approach below).
757
751
  *
758
752
  * Why strategy (b)?
759
753
  * -----------------
@@ -763,33 +757,49 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
763
757
  * (c) `stylesUrl` in manifest/remoteEntry.json — cleanest long-term; requires
764
758
  * protocol change to every remote's CI pipeline (out of scope for Wave A1).
765
759
  *
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`.
760
+ * Injection approach
761
+ * ------------------
762
+ * Preferred (Firefox 134+, Chrome 99+, Safari 17.2+):
763
+ * `<link rel="stylesheet" href="…" crossorigin="anonymous" layer="remote">`
764
+ *
765
+ * The `layer` attribute on `<link>` is feature-detected once at module init via:
766
+ * `'layer' in HTMLLinkElement.prototype`
767
+ * If the attribute is supported, the shell injects a non-render-blocking `<link>`
768
+ * element. The browser gets proper preload semantics and CORS headers flow through
769
+ * the normal stylesheet pipeline.
770
+ *
771
+ * Fallback (browsers without `link[layer]` support):
772
+ * `<style>@import url("…") layer(remote);</style>`
771
773
  *
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.
774
+ * CSS Cascade Level 5: `@import url("…") layer(remote)` is the ONLY valid way to
775
+ * place an @import inside a named cascade layer when using the `<style>` approach.
776
+ * The block-form `@layer remote { @import url("…"); }` is INVALID CSS — the spec
777
+ * forbids `@import` inside any block at-rule. Browsers silently drop such rules.
779
778
  *
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.
779
+ * Both code paths attach `data-fly-app` and `data-fly-href` attributes to the
780
+ * injected element so idempotency checks work uniformly.
784
781
  *
785
782
  * Layer order declaration
786
783
  * -----------------------
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:
784
+ * A one-time `<style data-fly-layers>` element is prepended to `<head>` at module
785
+ * init. It establishes the canonical layer order:
789
786
  * reset → designsystem → shell → remote → overrides
790
787
  * This guarantees that even if individual `@layer` blocks are injected in any
791
788
  * order at runtime, the cascade priority is always deterministic.
792
789
  *
790
+ * CSP nonce propagation
791
+ * ---------------------
792
+ * If a `<meta name="csp-nonce">` element is present, its content is read once at
793
+ * module init and applied to every injected `<style>` or `<link>` element. If no
794
+ * nonce meta is present, nothing is set (graceful degradation).
795
+ *
796
+ * URL validation
797
+ * --------------
798
+ * Discovered hrefs must be `http://`-, `https://`-, or `//`-prefixed. `javascript:`,
799
+ * `data:`, and `blob:` are rejected outright. The href's origin must also match the
800
+ * origin of `remoteBaseUrl` (same-origin as the remote), preventing a compromised
801
+ * `index.html` from pointing at an attacker domain.
802
+ *
793
803
  * Unload decision
794
804
  * ---------------
795
805
  * `unloadRemoteStyles` is exported for symmetry but should NOT be called on
@@ -800,63 +810,69 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
800
810
  *
801
811
  * Caveats
802
812
  * -------
803
- * - CORS: the remote's dev/prod server must serve `index.html` with a
804
- * permissive `Access-Control-Allow-Origin` header (or be same-origin via
805
- * the YARP gateway). The fetch uses `credentials: 'omit'` to avoid
806
- * credential-carrying preflights. The CSS file itself must also be CORS-
807
- * accessible since `@import` inside a `<style>` is subject to CORS checks.
808
- * - Race on rapid mount/unmount: if `loadRemoteStyles` is called a second time
809
- * for the same appId while the first fetch is still in-flight, the second
810
- * call joins the same in-flight Promise (idempotency guard at fetch level).
813
+ * - CORS: the remote's dev/prod server must serve `index.html` with a permissive
814
+ * `Access-Control-Allow-Origin` header (or be same-origin via the YARP gateway).
815
+ * The fetch uses `credentials: 'omit'` to avoid credential-carrying preflights.
816
+ * - Fetch timeout: the index.html fetch is capped at 5 seconds. On timeout the
817
+ * in-flight entry is cleared so a retry is possible on the next call.
818
+ * - Race on rapid mount/unmount: if `loadRemoteStyles` is called a second time for
819
+ * the same appId while the first fetch is still in-flight, the second call joins
820
+ * the same in-flight Promise.
811
821
  * - Angular hashing: Angular's production build hashes the stylesheet filename.
812
- * DOMParser picks the first `<link rel="stylesheet">` in `<head>`, which is
813
- * the single global stylesheet Angular emits. If a remote emits multiple
814
- * stylesheets, only the first is loaded — acceptable for Wave A1.
822
+ * DOMParser picks the first `<link rel="stylesheet">` in `<head>`, which is the
823
+ * single global stylesheet Angular emits. If a remote emits multiple stylesheets,
824
+ * only the first is loaded — acceptable for Wave A1.
815
825
  */
816
- /** Per-appId cache of the resolved stylesheet href (or `null` if not found). */
817
- const _resolvedHref = new Map();
818
- /** In-flight fetch promises keyed by appId — prevents duplicate fetches. */
819
- const _inFlight = new Map();
820
826
  /**
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.
827
+ * Feature detection: does the browser support the `layer` attribute on
828
+ * `<link>` elements? Probed once at module init.
829
+ * Firefox 134+, Chrome 99+, Safari 17.2+ all ship this.
824
830
  */
825
- let _layersDeclared = false;
831
+ const _linkLayerSupported = typeof HTMLLinkElement !== 'undefined' && 'layer' in HTMLLinkElement.prototype;
826
832
  /**
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.
833
+ * CSP nonce, read once from `<meta name="csp-nonce">` at module init.
834
+ * Applied to every injected `<style>` / `<link>` element. Null when absent.
835
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).
836
+ const _cspNonce = typeof document !== 'undefined'
837
+ ? (document.querySelector('meta[name="csp-nonce"]')?.content ?? null)
838
+ : null;
839
+ /** In-flight fetch promises keyed by appId — prevents duplicate fetches. */
840
+ const _inFlight = new Map();
841
+ // ---------------------------------------------------------------------------
842
+ // Layer order — injected immediately at module init (synchronous, runs once).
843
+ // ---------------------------------------------------------------------------
844
+ (function _ensureLayerOrder() {
845
+ if (typeof document === 'undefined')
846
+ return; // SSR guard
841
847
  if (document.head.querySelector('style[data-fly-layers]'))
842
848
  return;
843
849
  const style = document.createElement('style');
844
850
  style.setAttribute('data-fly-layers', '');
845
851
  style.textContent = '@layer reset, designsystem, shell, remote, overrides;';
852
+ if (_cspNonce)
853
+ style.nonce = _cspNonce;
846
854
  // Prepend so this always precedes any other layer-bearing <style> blocks.
847
855
  document.head.insertBefore(style, document.head.firstChild);
848
- }
856
+ })();
857
+ // ---------------------------------------------------------------------------
858
+ // Internal helpers
859
+ // ---------------------------------------------------------------------------
849
860
  /**
850
861
  * Discovers the hashed stylesheet href from the remote's `index.html`.
851
- * Returns `null` if no stylesheet `<link>` is found or the fetch fails.
862
+ * Applies a 5-second AbortController timeout. Clears `_inFlight` on abort so
863
+ * a retry is possible. Returns `null` on any failure.
852
864
  */
853
- async function _discoverStylesheetHref(remoteBaseUrl) {
865
+ async function _discoverStylesheetHref(appId, remoteBaseUrl) {
854
866
  const indexUrl = remoteBaseUrl.replace(/\/$/, '') + '/index.html';
867
+ const controller = new AbortController();
868
+ const timeoutId = setTimeout(() => controller.abort(), 5000);
855
869
  try {
856
870
  const res = await fetch(indexUrl, {
857
871
  credentials: 'omit',
858
872
  cache: 'default',
873
+ signal: controller.signal,
859
874
  });
875
+ clearTimeout(timeoutId);
860
876
  if (!res.ok)
861
877
  return null;
862
878
  const html = await res.text();
@@ -865,85 +881,163 @@ async function _discoverStylesheetHref(remoteBaseUrl) {
865
881
  const link = doc.head.querySelector('link[rel="stylesheet"]');
866
882
  if (!link?.href)
867
883
  return null;
868
- // `link.href` from DOMParser is resolved relative to the parser's base,
869
- // which defaults to `about:blank`. We get the raw `href` attribute instead.
884
+ // `link.href` from DOMParser resolves against `about:blank`. Use raw attribute.
870
885
  const rawHref = link.getAttribute('href') ?? '';
871
886
  if (!rawHref)
872
887
  return null;
873
- // If the remote emits an absolute URL, use it as-is; otherwise resolve
874
- // against remoteBaseUrl.
888
+ // Early-reject dangerous schemes before any URL resolution.
889
+ // Without this guard, `javascript:alert(1)` would be rewritten to
890
+ // `remoteBaseUrl/javascript:alert(1)` and silently pass origin validation.
891
+ const rawLower = rawHref.toLowerCase();
892
+ if (rawLower.startsWith('javascript:') ||
893
+ rawLower.startsWith('data:') ||
894
+ rawLower.startsWith('blob:')) {
895
+ console.warn(`[FlyOS] loadRemoteStyles: rejected unsafe stylesheet href "${rawHref}"`);
896
+ return null;
897
+ }
898
+ // Resolve to absolute URL for origin validation.
899
+ let resolvedHref;
875
900
  if (rawHref.startsWith('http://') || rawHref.startsWith('https://') || rawHref.startsWith('//')) {
876
- return rawHref;
901
+ resolvedHref = rawHref;
902
+ }
903
+ else {
904
+ resolvedHref = remoteBaseUrl.replace(/\/$/, '') + '/' + rawHref.replace(/^\//, '');
905
+ }
906
+ return _validateHref(resolvedHref, remoteBaseUrl);
907
+ }
908
+ catch {
909
+ clearTimeout(timeoutId);
910
+ // On abort or network error, clear in-flight so a retry is possible.
911
+ _inFlight.delete(appId);
912
+ return null;
913
+ }
914
+ }
915
+ /**
916
+ * Validates that a discovered href is safe to inject:
917
+ * - Must be `http://`, `https://`, or `//`-prefixed (no `javascript:`, `data:`, `blob:`).
918
+ * - Must share the same origin as `remoteBaseUrl`.
919
+ *
920
+ * Returns the href on success; logs a warning and returns `null` on failure.
921
+ */
922
+ function _validateHref(href, remoteBaseUrl) {
923
+ // Block dangerous schemes.
924
+ const lower = href.toLowerCase();
925
+ if (lower.startsWith('javascript:') ||
926
+ lower.startsWith('data:') ||
927
+ lower.startsWith('blob:')) {
928
+ console.warn(`[FlyOS] loadRemoteStyles: rejected unsafe stylesheet href "${href}"`);
929
+ return null;
930
+ }
931
+ // Must be absolute (http/https/protocol-relative).
932
+ if (!lower.startsWith('http://') &&
933
+ !lower.startsWith('https://') &&
934
+ !lower.startsWith('//')) {
935
+ console.warn(`[FlyOS] loadRemoteStyles: rejected non-absolute stylesheet href "${href}"`);
936
+ return null;
937
+ }
938
+ // Origin must match remoteBaseUrl — prevents a compromised index.html from
939
+ // redirecting styles at an attacker-controlled domain.
940
+ try {
941
+ const hrefOrigin = new URL(href.startsWith('//') ? 'https:' + href : href).origin;
942
+ const remoteOrigin = new URL(remoteBaseUrl).origin;
943
+ if (hrefOrigin !== remoteOrigin) {
944
+ console.warn(`[FlyOS] loadRemoteStyles: rejected cross-origin stylesheet href "${href}" ` +
945
+ `(expected origin "${remoteOrigin}", got "${hrefOrigin}")`);
946
+ return null;
877
947
  }
878
- return remoteBaseUrl.replace(/\/$/, '') + '/' + rawHref.replace(/^\//, '');
879
948
  }
880
949
  catch {
950
+ console.warn(`[FlyOS] loadRemoteStyles: could not parse href origin for "${href}"`);
881
951
  return null;
882
952
  }
953
+ return href;
883
954
  }
955
+ // ---------------------------------------------------------------------------
956
+ // Public API
957
+ // ---------------------------------------------------------------------------
884
958
  /**
885
959
  * Injects the remote's stylesheet into `document.head`.
886
960
  *
887
- * @param appId - Stable identifier for the remote app (matches `DesktopApp.id`).
961
+ * @param appId - Stable identifier for the remote app (matches `DesktopApp.id`).
888
962
  * @param remoteBaseUrl - Base URL of the remote, e.g. `https://circles.example.com`
889
963
  * or `http://localhost:7202`. Must NOT include `/remoteEntry.json`.
890
964
  *
891
965
  * The call is idempotent:
892
- * - If a `<style data-fly-app="appId">` whose content references the same href
893
- * already exists → no-op.
966
+ * - If an element with `data-fly-app="appId"` whose `data-fly-href` matches the
967
+ * discovered href already exists → no-op.
894
968
  * - If the href differs (hot upgrade) → existing element is replaced.
895
969
  * - If no stylesheet is found in `index.html` → no-op (logged as a warning).
896
970
  *
897
- * Injection shape:
971
+ * Injection shape (preferred — `link[layer]` supported):
972
+ * <link rel="stylesheet" data-fly-app="<appId>" data-fly-href="<href>"
973
+ * href="<href>" crossorigin="anonymous" layer="remote">
974
+ *
975
+ * Injection shape (fallback — `link[layer]` unsupported):
898
976
  * <style data-fly-app="<appId>" data-fly-href="<href>">
899
- * @layer remote { @import url("<href>"); }
977
+ * @import url("<href>") layer(remote);
900
978
  * </style>
901
979
  *
902
980
  * 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.
981
+ * element content so idempotency checks can compare the URL without parsing CSS.
904
982
  */
905
983
  async function loadRemoteStyles(appId, remoteBaseUrl) {
906
984
  if (typeof document === 'undefined')
907
985
  return; // SSR guard
908
- // Ensure the canonical layer order is declared before any remote layer is injected.
909
- _ensureLayerOrder();
910
986
  // Kick off or join an in-flight fetch for this appId.
987
+ // Note: _resolvedHref cache is intentionally absent — always re-discover so
988
+ // the browser's HTTP cache handles cost (index.html is tiny) and hot-deploys
989
+ // are picked up without a page reload.
911
990
  let fetchPromise = _inFlight.get(appId);
912
991
  if (!fetchPromise) {
913
- if (_resolvedHref.has(appId)) {
914
- // Already resolved in a previous call — skip the fetch.
915
- fetchPromise = Promise.resolve(_resolvedHref.get(appId) ?? null);
916
- }
917
- else {
918
- fetchPromise = _discoverStylesheetHref(remoteBaseUrl);
919
- _inFlight.set(appId, fetchPromise);
920
- }
992
+ fetchPromise = _discoverStylesheetHref(appId, remoteBaseUrl);
993
+ _inFlight.set(appId, fetchPromise);
921
994
  }
922
995
  const href = await fetchPromise;
923
996
  _inFlight.delete(appId);
924
- _resolvedHref.set(appId, href);
925
997
  if (!href) {
926
998
  console.warn(`[FlyOS] loadRemoteStyles: no stylesheet found in ${remoteBaseUrl}/index.html for appId="${appId}"`);
927
999
  return;
928
1000
  }
929
- const selector = `style[data-fly-app="${CSS.escape(appId)}"]`;
930
- const existing = document.head.querySelector(selector);
1001
+ // Check both <link> and <style> selectors — handle upgrades from the old fallback path.
1002
+ const escapedId = CSS.escape(appId);
1003
+ const existing = document.head.querySelector(`link[data-fly-app="${escapedId}"], style[data-fly-app="${escapedId}"]`);
931
1004
  if (existing) {
932
1005
  if (existing.getAttribute('data-fly-href') === href)
933
1006
  return; // identical — no-op
934
- // Hot upgrade: replace the entire element so the @import URL updates atomically.
1007
+ // Hot upgrade: remove old element (could be <link> or <style>) atomically.
935
1008
  existing.remove();
936
1009
  }
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);
1010
+ if (_linkLayerSupported) {
1011
+ // Preferred path: non-render-blocking <link> with native layer= attribute.
1012
+ // Firefox 134+, Chrome 99+, Safari 17.2+ all support this.
1013
+ const link = document.createElement('link');
1014
+ link.rel = 'stylesheet';
1015
+ link.setAttribute('data-fly-app', appId);
1016
+ link.setAttribute('data-fly-href', href);
1017
+ link.href = href;
1018
+ link.crossOrigin = 'anonymous';
1019
+ link['layer'] = 'remote';
1020
+ if (_cspNonce)
1021
+ link.nonce = _cspNonce;
1022
+ document.head.appendChild(link);
1023
+ }
1024
+ else {
1025
+ // Fallback path: <style> with @import layer(remote) for older browsers.
1026
+ // CSS Cascade Level 5: `@import url("…") layer(remote)` is the only valid
1027
+ // way to place an @import inside a named cascade layer. Block-form
1028
+ // `@layer remote { @import … }` is invalid and silently dropped by browsers.
1029
+ const style = document.createElement('style');
1030
+ style.setAttribute('data-fly-app', appId);
1031
+ style.setAttribute('data-fly-href', href);
1032
+ style.textContent = `@import url("${href}") layer(remote);`;
1033
+ if (_cspNonce)
1034
+ style.nonce = _cspNonce;
1035
+ document.head.appendChild(style);
1036
+ }
944
1037
  }
945
1038
  /**
946
- * Removes the injected stylesheet for `appId` from `document.head`.
1039
+ * Removes the injected stylesheet element (either `<link>` or `<style>`) for
1040
+ * `appId` from `document.head` and clears all internal state for this appId.
947
1041
  *
948
1042
  * NOTE: Prefer NOT calling this on normal window close — keeping styles loaded
949
1043
  * prevents FOUC flicker when the user reopens the same app. Call only if you
@@ -952,9 +1046,17 @@ async function loadRemoteStyles(appId, remoteBaseUrl) {
952
1046
  function unloadRemoteStyles(appId) {
953
1047
  if (typeof document === 'undefined')
954
1048
  return; // SSR guard
955
- const style = document.head.querySelector(`style[data-fly-app="${CSS.escape(appId)}"]`);
956
- style?.parentNode?.removeChild(style);
957
- _resolvedHref.delete(appId);
1049
+ const escapedId = CSS.escape(appId);
1050
+ // Remove <link> element (preferred path) if present.
1051
+ document.head
1052
+ .querySelector(`link[data-fly-app="${escapedId}"]`)
1053
+ ?.remove();
1054
+ // Remove <style> element (fallback path) if present.
1055
+ document.head
1056
+ .querySelector(`style[data-fly-app="${escapedId}"]`)
1057
+ ?.remove();
1058
+ // Clear in-flight so a future call can retry.
1059
+ _inFlight.delete(appId);
958
1060
  }
959
1061
 
960
1062
  /**
@@ -2767,7 +2869,7 @@ class FlyImageUploadComponent {
2767
2869
  </div>
2768
2870
  }
2769
2871
  </div>
2770
- `, isInline: true, styles: [".cropper-container{-webkit-touch-callout:none;direction:ltr;font-size:0;line-height:0;position:relative;touch-action:none;-webkit-user-select:none;user-select:none}.cropper-container img{backface-visibility:hidden;display:block;height:100%;image-orientation:0deg;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;width:100%}.cropper-canvas,.cropper-crop-box,.cropper-drag-box,.cropper-modal,.cropper-wrap-box{inset:0;position:absolute}.cropper-canvas,.cropper-wrap-box{overflow:hidden}.cropper-drag-box{background-color:#fff;opacity:0}.cropper-modal{background-color:#000;opacity:.5}.cropper-view-box{display:block;height:100%;outline:1px solid #39f;outline-color:#3399ffbf;overflow:hidden;width:100%}.cropper-dashed{border:0 dashed #eee;display:block;opacity:.5;position:absolute}.cropper-dashed.dashed-h{border-bottom-width:1px;border-top-width:1px;height:33.33333%;left:0;top:33.33333%;width:100%}.cropper-dashed.dashed-v{border-left-width:1px;border-right-width:1px;height:100%;left:33.33333%;top:0;width:33.33333%}.cropper-center{display:block;height:0;left:50%;opacity:.75;position:absolute;top:50%;width:0}.cropper-center:after,.cropper-center:before{background-color:#eee;content:\" \";display:block;position:absolute}.cropper-center:before{height:1px;left:-3px;top:0;width:7px}.cropper-center:after{height:7px;left:0;top:-3px;width:1px}.cropper-face,.cropper-line,.cropper-point{display:block;height:100%;opacity:.1;position:absolute;width:100%}.cropper-face{background-color:#fff;left:0;top:0}.cropper-line{background-color:#39f}.cropper-line.line-e{cursor:ew-resize;right:-3px;top:0;width:5px}.cropper-line.line-n{cursor:ns-resize;height:5px;left:0;top:-3px}.cropper-line.line-w{cursor:ew-resize;left:-3px;top:0;width:5px}.cropper-line.line-s{bottom:-3px;cursor:ns-resize;height:5px;left:0}.cropper-point{background-color:#39f;height:5px;opacity:.75;width:5px}.cropper-point.point-e{cursor:ew-resize;margin-top:-3px;right:-3px;top:50%}.cropper-point.point-n{cursor:ns-resize;left:50%;margin-left:-3px;top:-3px}.cropper-point.point-w{cursor:ew-resize;left:-3px;margin-top:-3px;top:50%}.cropper-point.point-s{bottom:-3px;cursor:s-resize;left:50%;margin-left:-3px}.cropper-point.point-ne{cursor:nesw-resize;right:-3px;top:-3px}.cropper-point.point-nw{cursor:nwse-resize;left:-3px;top:-3px}.cropper-point.point-sw{bottom:-3px;cursor:nesw-resize;left:-3px}.cropper-point.point-se{bottom:-3px;cursor:nwse-resize;height:20px;opacity:1;right:-3px;width:20px}.cropper-invisible{opacity:0}.cropper-bg{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC)}.cropper-hide{display:block;height:0;position:absolute;width:0}.cropper-hidden{display:none!important}.cropper-move{cursor:move}.cropper-crop{cursor:crosshair}.cropper-disabled .cropper-drag-box,.cropper-disabled .cropper-face,.cropper-disabled .cropper-line,.cropper-disabled .cropper-point{cursor:not-allowed}\n", ".fly-image-upload{display:flex;flex-direction:column;gap:6px}.fly-image-upload__hidden{display:none}.fly-image-upload__dropzone{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:24px;border:2px dashed var(--separator-primary, #e5e7eb);border-radius:12px;background:var(--fill-quaternary, #f9fafb);cursor:pointer;transition:border-color .15s,background .15s;min-height:120px}.fly-image-upload__dropzone:hover{border-color:var(--accent-primary, #0071e3);background:var(--fill-tertiary, #f3f4f6)}.fly-image-upload__icon{font-size:28px;color:var(--label-tertiary, #9ca3af)}.fly-image-upload__label{font-size:13px;color:var(--label-secondary, #6b7280)}.fly-image-upload__hint{font-size:11px;color:var(--label-tertiary, #9ca3af)}.fly-image-upload__preview{position:relative;border-radius:12px;overflow:hidden}.fly-image-upload__img{display:block;width:100%;max-height:280px;object-fit:cover;border-radius:12px}.fly-image-upload__overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;gap:12px;background:#00000059;opacity:0;transition:opacity .15s}.fly-image-upload__preview:hover .fly-image-upload__overlay{opacity:1}.fly-image-upload__action-btn{width:36px;height:36px;border-radius:50%;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;background:#ffffffe6;color:var(--label-primary, #1f2937);font-size:14px;transition:background .15s}.fly-image-upload__action-btn:hover{background:#fff}.fly-image-upload__action-btn--danger:hover{background:#fee2e2;color:#dc2626}.fly-image-upload__error{font-size:12px;color:var(--system-red, #ef4444)}.fly-image-upload__crop-backdrop{position:fixed;inset:0;z-index:10000;display:flex;align-items:center;justify-content:center;background:#0009;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.fly-image-upload__crop-modal{background:var(--bg-primary, #fff);color:var(--label-primary, #1f2937);border-radius:16px;overflow:hidden;max-width:700px;width:90vw;box-shadow:0 20px 60px #0006;border:1px solid var(--separator-primary, rgba(0,0,0,.1))}.fly-image-upload__crop-header{padding:16px 20px;border-bottom:1px solid var(--separator-primary, #e5e7eb);background:var(--fill-quaternary, transparent)}.fly-image-upload__crop-header h3{margin:0;font-size:16px;font-weight:600;color:var(--label-primary, #1f2937)}.fly-image-upload__crop-body{max-height:60vh;overflow:hidden;background:var(--fill-tertiary, #f3f4f6)}.fly-image-upload__crop-body img{display:block;max-width:100%}.fly-image-upload__crop-footer{display:flex;justify-content:flex-end;gap:10px;padding:14px 20px;border-top:1px solid var(--separator-primary, #e5e7eb);background:var(--fill-quaternary, transparent)}html.spatial-theme .fly-image-upload__crop-modal{background:#ffffffa6;backdrop-filter:blur(40px) saturate(1.8);-webkit-backdrop-filter:blur(40px) saturate(1.8);border:1px solid rgba(255,255,255,.4)}html.spatial-theme .fly-image-upload__crop-header,html.spatial-theme .fly-image-upload__crop-footer{background:transparent}html.spatial-theme .fly-image-upload__crop-body{background:#0000000d}html.dark-theme .fly-image-upload__crop-modal{background:var(--bg-primary, #1c1c1e);border:1px solid var(--separator-primary, rgba(255,255,255,.1))}html.dark-theme .fly-image-upload__crop-body{background:var(--fill-tertiary, #2c2c2e)}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
2872
+ `, isInline: true, styles: [".cropper-container{-webkit-touch-callout:none;direction:ltr;font-size:0;line-height:0;position:relative;touch-action:none;-webkit-user-select:none;user-select:none}.cropper-container img{backface-visibility:hidden;display:block;height:100%;image-orientation:0deg;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;width:100%}.cropper-canvas,.cropper-crop-box,.cropper-drag-box,.cropper-modal,.cropper-wrap-box{inset:0;position:absolute}.cropper-canvas,.cropper-wrap-box{overflow:hidden}.cropper-drag-box{background-color:#fff;opacity:0}.cropper-modal{background-color:#000;opacity:.5}.cropper-view-box{display:block;height:100%;outline:1px solid #39f;outline-color:#3399ffbf;overflow:hidden;width:100%}.cropper-dashed{border:0 dashed #eee;display:block;opacity:.5;position:absolute}.cropper-dashed.dashed-h{border-bottom-width:1px;border-top-width:1px;height:33.33333%;left:0;top:33.33333%;width:100%}.cropper-dashed.dashed-v{border-left-width:1px;border-right-width:1px;height:100%;left:33.33333%;top:0;width:33.33333%}.cropper-center{display:block;height:0;left:50%;opacity:.75;position:absolute;top:50%;width:0}.cropper-center:after,.cropper-center:before{background-color:#eee;content:\" \";display:block;position:absolute}.cropper-center:before{height:1px;left:-3px;top:0;width:7px}.cropper-center:after{height:7px;left:0;top:-3px;width:1px}.cropper-face,.cropper-line,.cropper-point{display:block;height:100%;opacity:.1;position:absolute;width:100%}.cropper-face{background-color:#fff;left:0;top:0}.cropper-line{background-color:#39f}.cropper-line.line-e{cursor:ew-resize;right:-3px;top:0;width:5px}.cropper-line.line-n{cursor:ns-resize;height:5px;left:0;top:-3px}.cropper-line.line-w{cursor:ew-resize;left:-3px;top:0;width:5px}.cropper-line.line-s{bottom:-3px;cursor:ns-resize;height:5px;left:0}.cropper-point{background-color:#39f;height:5px;opacity:.75;width:5px}.cropper-point.point-e{cursor:ew-resize;margin-top:-3px;right:-3px;top:50%}.cropper-point.point-n{cursor:ns-resize;left:50%;margin-left:-3px;top:-3px}.cropper-point.point-w{cursor:ew-resize;left:-3px;margin-top:-3px;top:50%}.cropper-point.point-s{bottom:-3px;cursor:s-resize;left:50%;margin-left:-3px}.cropper-point.point-ne{cursor:nesw-resize;right:-3px;top:-3px}.cropper-point.point-nw{cursor:nwse-resize;left:-3px;top:-3px}.cropper-point.point-sw{bottom:-3px;cursor:nesw-resize;left:-3px}.cropper-point.point-se{bottom:-3px;cursor:nwse-resize;height:20px;opacity:1;right:-3px;width:20px}.cropper-invisible{opacity:0}.cropper-bg{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC)}.cropper-hide{display:block;height:0;position:absolute;width:0}.cropper-hidden{display:none!important}.cropper-move{cursor:move}.cropper-crop{cursor:crosshair}.cropper-disabled .cropper-drag-box,.cropper-disabled .cropper-face,.cropper-disabled .cropper-line,.cropper-disabled .cropper-point{cursor:not-allowed}\n", ".fly-image-upload{display:flex;flex-direction:column;gap:6px}.fly-image-upload__hidden{display:none}.fly-image-upload__dropzone{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:24px;border:2px dashed var(--separator-primary, #e5e7eb);border-radius:12px;background:var(--fill-quaternary, #f9fafb);cursor:pointer;transition:border-color .15s,background .15s;min-height:120px}.fly-image-upload__dropzone:hover{border-color:var(--accent-primary, #0071e3);background:var(--fill-tertiary, #f3f4f6)}.fly-image-upload__icon{font-size:28px;color:var(--label-tertiary, #9ca3af)}.fly-image-upload__label{font-size:13px;color:var(--label-secondary, #6b7280)}.fly-image-upload__hint{font-size:11px;color:var(--label-tertiary, #9ca3af)}.fly-image-upload__preview{position:relative;border-radius:12px;overflow:hidden}.fly-image-upload__img{display:block;width:100%;max-height:280px;object-fit:cover;border-radius:12px}.fly-image-upload__overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;gap:12px;background:#00000059;opacity:0;transition:opacity .15s}.fly-image-upload__preview:hover .fly-image-upload__overlay{opacity:1}.fly-image-upload__action-btn{width:36px;height:36px;border-radius:50%;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;background:#ffffffe6;color:var(--label-primary, #1f2937);font-size:14px;transition:background .15s}.fly-image-upload__action-btn:hover{background:#fff}.fly-image-upload__action-btn--danger:hover{background:#fee2e2;color:#dc2626}.fly-image-upload__error{font-size:12px;color:var(--system-red, #ef4444)}.fly-image-upload__crop-backdrop{position:fixed;inset:0;z-index:10000;display:flex;align-items:center;justify-content:center;background:#0009;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.fly-image-upload__crop-modal{background:var(--bg-primary, #fff);color:var(--label-primary, #1f2937);border-radius:16px;overflow:hidden;max-width:700px;width:90vw;box-shadow:0 20px 60px #0006;border:1px solid var(--separator-primary, rgba(0,0,0,.1))}.fly-image-upload__crop-header{padding:16px 20px;border-bottom:1px solid var(--separator-primary, #e5e7eb);background:var(--fill-quaternary, transparent)}.fly-image-upload__crop-header h3{margin:0;font-size:16px;font-weight:600;color:var(--label-primary, #1f2937)}.fly-image-upload__crop-body{max-height:60vh;overflow:hidden;background:var(--fill-tertiary, #f3f4f6)}.fly-image-upload__crop-body img{display:block;max-width:100%}.fly-image-upload__crop-footer{display:flex;justify-content:flex-end;gap:10px;padding:14px 20px;border-top:1px solid var(--separator-primary, #e5e7eb);background:var(--fill-quaternary, transparent)}html.dark-theme .fly-image-upload__crop-modal{background:var(--bg-primary, #1c1c1e);border:1px solid var(--separator-primary, rgba(255,255,255,.1))}html.dark-theme .fly-image-upload__crop-body{background:var(--fill-tertiary, #2c2c2e)}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
2771
2873
  }
2772
2874
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyImageUploadComponent, decorators: [{
2773
2875
  type: Component,
@@ -2821,7 +2923,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
2821
2923
  </div>
2822
2924
  }
2823
2925
  </div>
2824
- `, styles: [".cropper-container{-webkit-touch-callout:none;direction:ltr;font-size:0;line-height:0;position:relative;touch-action:none;-webkit-user-select:none;user-select:none}.cropper-container img{backface-visibility:hidden;display:block;height:100%;image-orientation:0deg;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;width:100%}.cropper-canvas,.cropper-crop-box,.cropper-drag-box,.cropper-modal,.cropper-wrap-box{inset:0;position:absolute}.cropper-canvas,.cropper-wrap-box{overflow:hidden}.cropper-drag-box{background-color:#fff;opacity:0}.cropper-modal{background-color:#000;opacity:.5}.cropper-view-box{display:block;height:100%;outline:1px solid #39f;outline-color:#3399ffbf;overflow:hidden;width:100%}.cropper-dashed{border:0 dashed #eee;display:block;opacity:.5;position:absolute}.cropper-dashed.dashed-h{border-bottom-width:1px;border-top-width:1px;height:33.33333%;left:0;top:33.33333%;width:100%}.cropper-dashed.dashed-v{border-left-width:1px;border-right-width:1px;height:100%;left:33.33333%;top:0;width:33.33333%}.cropper-center{display:block;height:0;left:50%;opacity:.75;position:absolute;top:50%;width:0}.cropper-center:after,.cropper-center:before{background-color:#eee;content:\" \";display:block;position:absolute}.cropper-center:before{height:1px;left:-3px;top:0;width:7px}.cropper-center:after{height:7px;left:0;top:-3px;width:1px}.cropper-face,.cropper-line,.cropper-point{display:block;height:100%;opacity:.1;position:absolute;width:100%}.cropper-face{background-color:#fff;left:0;top:0}.cropper-line{background-color:#39f}.cropper-line.line-e{cursor:ew-resize;right:-3px;top:0;width:5px}.cropper-line.line-n{cursor:ns-resize;height:5px;left:0;top:-3px}.cropper-line.line-w{cursor:ew-resize;left:-3px;top:0;width:5px}.cropper-line.line-s{bottom:-3px;cursor:ns-resize;height:5px;left:0}.cropper-point{background-color:#39f;height:5px;opacity:.75;width:5px}.cropper-point.point-e{cursor:ew-resize;margin-top:-3px;right:-3px;top:50%}.cropper-point.point-n{cursor:ns-resize;left:50%;margin-left:-3px;top:-3px}.cropper-point.point-w{cursor:ew-resize;left:-3px;margin-top:-3px;top:50%}.cropper-point.point-s{bottom:-3px;cursor:s-resize;left:50%;margin-left:-3px}.cropper-point.point-ne{cursor:nesw-resize;right:-3px;top:-3px}.cropper-point.point-nw{cursor:nwse-resize;left:-3px;top:-3px}.cropper-point.point-sw{bottom:-3px;cursor:nesw-resize;left:-3px}.cropper-point.point-se{bottom:-3px;cursor:nwse-resize;height:20px;opacity:1;right:-3px;width:20px}.cropper-invisible{opacity:0}.cropper-bg{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC)}.cropper-hide{display:block;height:0;position:absolute;width:0}.cropper-hidden{display:none!important}.cropper-move{cursor:move}.cropper-crop{cursor:crosshair}.cropper-disabled .cropper-drag-box,.cropper-disabled .cropper-face,.cropper-disabled .cropper-line,.cropper-disabled .cropper-point{cursor:not-allowed}\n", ".fly-image-upload{display:flex;flex-direction:column;gap:6px}.fly-image-upload__hidden{display:none}.fly-image-upload__dropzone{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:24px;border:2px dashed var(--separator-primary, #e5e7eb);border-radius:12px;background:var(--fill-quaternary, #f9fafb);cursor:pointer;transition:border-color .15s,background .15s;min-height:120px}.fly-image-upload__dropzone:hover{border-color:var(--accent-primary, #0071e3);background:var(--fill-tertiary, #f3f4f6)}.fly-image-upload__icon{font-size:28px;color:var(--label-tertiary, #9ca3af)}.fly-image-upload__label{font-size:13px;color:var(--label-secondary, #6b7280)}.fly-image-upload__hint{font-size:11px;color:var(--label-tertiary, #9ca3af)}.fly-image-upload__preview{position:relative;border-radius:12px;overflow:hidden}.fly-image-upload__img{display:block;width:100%;max-height:280px;object-fit:cover;border-radius:12px}.fly-image-upload__overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;gap:12px;background:#00000059;opacity:0;transition:opacity .15s}.fly-image-upload__preview:hover .fly-image-upload__overlay{opacity:1}.fly-image-upload__action-btn{width:36px;height:36px;border-radius:50%;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;background:#ffffffe6;color:var(--label-primary, #1f2937);font-size:14px;transition:background .15s}.fly-image-upload__action-btn:hover{background:#fff}.fly-image-upload__action-btn--danger:hover{background:#fee2e2;color:#dc2626}.fly-image-upload__error{font-size:12px;color:var(--system-red, #ef4444)}.fly-image-upload__crop-backdrop{position:fixed;inset:0;z-index:10000;display:flex;align-items:center;justify-content:center;background:#0009;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.fly-image-upload__crop-modal{background:var(--bg-primary, #fff);color:var(--label-primary, #1f2937);border-radius:16px;overflow:hidden;max-width:700px;width:90vw;box-shadow:0 20px 60px #0006;border:1px solid var(--separator-primary, rgba(0,0,0,.1))}.fly-image-upload__crop-header{padding:16px 20px;border-bottom:1px solid var(--separator-primary, #e5e7eb);background:var(--fill-quaternary, transparent)}.fly-image-upload__crop-header h3{margin:0;font-size:16px;font-weight:600;color:var(--label-primary, #1f2937)}.fly-image-upload__crop-body{max-height:60vh;overflow:hidden;background:var(--fill-tertiary, #f3f4f6)}.fly-image-upload__crop-body img{display:block;max-width:100%}.fly-image-upload__crop-footer{display:flex;justify-content:flex-end;gap:10px;padding:14px 20px;border-top:1px solid var(--separator-primary, #e5e7eb);background:var(--fill-quaternary, transparent)}html.spatial-theme .fly-image-upload__crop-modal{background:#ffffffa6;backdrop-filter:blur(40px) saturate(1.8);-webkit-backdrop-filter:blur(40px) saturate(1.8);border:1px solid rgba(255,255,255,.4)}html.spatial-theme .fly-image-upload__crop-header,html.spatial-theme .fly-image-upload__crop-footer{background:transparent}html.spatial-theme .fly-image-upload__crop-body{background:#0000000d}html.dark-theme .fly-image-upload__crop-modal{background:var(--bg-primary, #1c1c1e);border:1px solid var(--separator-primary, rgba(255,255,255,.1))}html.dark-theme .fly-image-upload__crop-body{background:var(--fill-tertiary, #2c2c2e)}\n"] }]
2926
+ `, styles: [".cropper-container{-webkit-touch-callout:none;direction:ltr;font-size:0;line-height:0;position:relative;touch-action:none;-webkit-user-select:none;user-select:none}.cropper-container img{backface-visibility:hidden;display:block;height:100%;image-orientation:0deg;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;width:100%}.cropper-canvas,.cropper-crop-box,.cropper-drag-box,.cropper-modal,.cropper-wrap-box{inset:0;position:absolute}.cropper-canvas,.cropper-wrap-box{overflow:hidden}.cropper-drag-box{background-color:#fff;opacity:0}.cropper-modal{background-color:#000;opacity:.5}.cropper-view-box{display:block;height:100%;outline:1px solid #39f;outline-color:#3399ffbf;overflow:hidden;width:100%}.cropper-dashed{border:0 dashed #eee;display:block;opacity:.5;position:absolute}.cropper-dashed.dashed-h{border-bottom-width:1px;border-top-width:1px;height:33.33333%;left:0;top:33.33333%;width:100%}.cropper-dashed.dashed-v{border-left-width:1px;border-right-width:1px;height:100%;left:33.33333%;top:0;width:33.33333%}.cropper-center{display:block;height:0;left:50%;opacity:.75;position:absolute;top:50%;width:0}.cropper-center:after,.cropper-center:before{background-color:#eee;content:\" \";display:block;position:absolute}.cropper-center:before{height:1px;left:-3px;top:0;width:7px}.cropper-center:after{height:7px;left:0;top:-3px;width:1px}.cropper-face,.cropper-line,.cropper-point{display:block;height:100%;opacity:.1;position:absolute;width:100%}.cropper-face{background-color:#fff;left:0;top:0}.cropper-line{background-color:#39f}.cropper-line.line-e{cursor:ew-resize;right:-3px;top:0;width:5px}.cropper-line.line-n{cursor:ns-resize;height:5px;left:0;top:-3px}.cropper-line.line-w{cursor:ew-resize;left:-3px;top:0;width:5px}.cropper-line.line-s{bottom:-3px;cursor:ns-resize;height:5px;left:0}.cropper-point{background-color:#39f;height:5px;opacity:.75;width:5px}.cropper-point.point-e{cursor:ew-resize;margin-top:-3px;right:-3px;top:50%}.cropper-point.point-n{cursor:ns-resize;left:50%;margin-left:-3px;top:-3px}.cropper-point.point-w{cursor:ew-resize;left:-3px;margin-top:-3px;top:50%}.cropper-point.point-s{bottom:-3px;cursor:s-resize;left:50%;margin-left:-3px}.cropper-point.point-ne{cursor:nesw-resize;right:-3px;top:-3px}.cropper-point.point-nw{cursor:nwse-resize;left:-3px;top:-3px}.cropper-point.point-sw{bottom:-3px;cursor:nesw-resize;left:-3px}.cropper-point.point-se{bottom:-3px;cursor:nwse-resize;height:20px;opacity:1;right:-3px;width:20px}.cropper-invisible{opacity:0}.cropper-bg{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC)}.cropper-hide{display:block;height:0;position:absolute;width:0}.cropper-hidden{display:none!important}.cropper-move{cursor:move}.cropper-crop{cursor:crosshair}.cropper-disabled .cropper-drag-box,.cropper-disabled .cropper-face,.cropper-disabled .cropper-line,.cropper-disabled .cropper-point{cursor:not-allowed}\n", ".fly-image-upload{display:flex;flex-direction:column;gap:6px}.fly-image-upload__hidden{display:none}.fly-image-upload__dropzone{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:24px;border:2px dashed var(--separator-primary, #e5e7eb);border-radius:12px;background:var(--fill-quaternary, #f9fafb);cursor:pointer;transition:border-color .15s,background .15s;min-height:120px}.fly-image-upload__dropzone:hover{border-color:var(--accent-primary, #0071e3);background:var(--fill-tertiary, #f3f4f6)}.fly-image-upload__icon{font-size:28px;color:var(--label-tertiary, #9ca3af)}.fly-image-upload__label{font-size:13px;color:var(--label-secondary, #6b7280)}.fly-image-upload__hint{font-size:11px;color:var(--label-tertiary, #9ca3af)}.fly-image-upload__preview{position:relative;border-radius:12px;overflow:hidden}.fly-image-upload__img{display:block;width:100%;max-height:280px;object-fit:cover;border-radius:12px}.fly-image-upload__overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;gap:12px;background:#00000059;opacity:0;transition:opacity .15s}.fly-image-upload__preview:hover .fly-image-upload__overlay{opacity:1}.fly-image-upload__action-btn{width:36px;height:36px;border-radius:50%;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;background:#ffffffe6;color:var(--label-primary, #1f2937);font-size:14px;transition:background .15s}.fly-image-upload__action-btn:hover{background:#fff}.fly-image-upload__action-btn--danger:hover{background:#fee2e2;color:#dc2626}.fly-image-upload__error{font-size:12px;color:var(--system-red, #ef4444)}.fly-image-upload__crop-backdrop{position:fixed;inset:0;z-index:10000;display:flex;align-items:center;justify-content:center;background:#0009;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.fly-image-upload__crop-modal{background:var(--bg-primary, #fff);color:var(--label-primary, #1f2937);border-radius:16px;overflow:hidden;max-width:700px;width:90vw;box-shadow:0 20px 60px #0006;border:1px solid var(--separator-primary, rgba(0,0,0,.1))}.fly-image-upload__crop-header{padding:16px 20px;border-bottom:1px solid var(--separator-primary, #e5e7eb);background:var(--fill-quaternary, transparent)}.fly-image-upload__crop-header h3{margin:0;font-size:16px;font-weight:600;color:var(--label-primary, #1f2937)}.fly-image-upload__crop-body{max-height:60vh;overflow:hidden;background:var(--fill-tertiary, #f3f4f6)}.fly-image-upload__crop-body img{display:block;max-width:100%}.fly-image-upload__crop-footer{display:flex;justify-content:flex-end;gap:10px;padding:14px 20px;border-top:1px solid var(--separator-primary, #e5e7eb);background:var(--fill-quaternary, transparent)}html.dark-theme .fly-image-upload__crop-modal{background:var(--bg-primary, #1c1c1e);border:1px solid var(--separator-primary, rgba(255,255,255,.1))}html.dark-theme .fly-image-upload__crop-body{background:var(--fill-tertiary, #2c2c2e)}\n"] }]
2825
2927
  }], ctorParameters: () => [], propDecorators: { aspectRatio: [{ type: i0.Input, args: [{ isSignal: true, alias: "aspectRatio", required: false }] }], maxSizeBytes: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSizeBytes", required: false }] }], currentImageId: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentImageId", required: false }] }], sourceApp: [{ type: i0.Input, args: [{ isSignal: true, alias: "sourceApp", required: false }] }], sourceEntityType: [{ type: i0.Input, args: [{ isSignal: true, alias: "sourceEntityType", required: false }] }], sourceEntityId: [{ type: i0.Input, args: [{ isSignal: true, alias: "sourceEntityId", required: false }] }], uploaded: [{ type: i0.Output, args: ["uploaded"] }], removed: [{ type: i0.Output, args: ["removed"] }], fileInput: [{ type: i0.ViewChild, args: ['fileInput', { isSignal: true }] }], cropImage: [{ type: i0.ViewChild, args: ['cropImage', { isSignal: true }] }] } });
2826
2928
 
2827
2929
  const FILE_ICONS = {