@mohamedatia/fly-design-system 2.7.4 → 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,34 +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 — `@import url("...") layer(remote)`
767
- * --------------------------------------------------------
768
- * Remote stylesheets are injected via `@import url("…") layer(remote)` (CSS
769
- * Cascade Level 5). This keeps remote styles in a named cascade layer so the
770
- * 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.
771
770
  *
772
- * The block-form `@layer remote { @import url("…"); }` is INVALID CSS — the
773
- * spec forbids `@import` inside any block at-rule. Browsers silently drop such
774
- * rules, causing remote stylesheets to never load. The correct syntax is the
775
- * `layer()` modifier on a top-level `@import`.
771
+ * Fallback (browsers without `link[layer]` support):
772
+ * `<style>@import url("…") layer(remote);</style>`
776
773
  *
777
- * Browser support: Chrome 99+, Firefox 97+, Safari 15.4+ all current engines.
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.
778
778
  *
779
- * Alternative approaches rejected:
780
- * - Fetch+inline: relative `url(...)` paths inside the remote CSS would
781
- * resolve against the shell origin instead of the remote origin, breaking
782
- * fonts and images. Dealbreaker without a full URL-rewrite pass.
783
- * - `<link layer="remote">`: the `layer` attribute on `<link>` is not yet
784
- * shipped in Firefox (as of 2025-05). Non-portable.
779
+ * Both code paths attach `data-fly-app` and `data-fly-href` attributes to the
780
+ * injected element so idempotency checks work uniformly.
785
781
  *
786
782
  * Layer order declaration
787
783
  * -----------------------
788
- * A one-time `<style data-fly-layers>` element is prepended to `<head>` the first
789
- * 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:
790
786
  * reset → designsystem → shell → remote → overrides
791
787
  * This guarantees that even if individual `@layer` blocks are injected in any
792
788
  * order at runtime, the cascade priority is always deterministic.
793
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
+ *
794
803
  * Unload decision
795
804
  * ---------------
796
805
  * `unloadRemoteStyles` is exported for symmetry but should NOT be called on
@@ -801,63 +810,69 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
801
810
  *
802
811
  * Caveats
803
812
  * -------
804
- * - CORS: the remote's dev/prod server must serve `index.html` with a
805
- * permissive `Access-Control-Allow-Origin` header (or be same-origin via
806
- * the YARP gateway). The fetch uses `credentials: 'omit'` to avoid
807
- * credential-carrying preflights. The CSS file itself must also be CORS-
808
- * accessible since `@import` inside a `<style>` is subject to CORS checks.
809
- * - Race on rapid mount/unmount: if `loadRemoteStyles` is called a second time
810
- * for the same appId while the first fetch is still in-flight, the second
811
- * 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.
812
821
  * - Angular hashing: Angular's production build hashes the stylesheet filename.
813
- * DOMParser picks the first `<link rel="stylesheet">` in `<head>`, which is
814
- * the single global stylesheet Angular emits. If a remote emits multiple
815
- * 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.
816
825
  */
817
- /** Per-appId cache of the resolved stylesheet href (or `null` if not found). */
818
- const _resolvedHref = new Map();
819
- /** In-flight fetch promises keyed by appId — prevents duplicate fetches. */
820
- const _inFlight = new Map();
821
826
  /**
822
- * Whether the canonical layer-order declaration has already been injected.
823
- * Module-level so it survives across multiple `loadRemoteStyles` calls within
824
- * 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.
825
830
  */
826
- let _layersDeclared = false;
831
+ const _linkLayerSupported = typeof HTMLLinkElement !== 'undefined' && 'layer' in HTMLLinkElement.prototype;
827
832
  /**
828
- * Injects a single `<style data-fly-layers>` element at the top of `<head>`
829
- * that establishes the canonical cascade-layer priority order for the shell:
830
- *
831
- * reset → designsystem → shell → remote → overrides
832
- *
833
- * Any subsequent `@layer X { … }` block lands in the already-established slot
834
- * regardless of injection order, so late-arriving remote stylesheets never
835
- * "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.
836
835
  */
837
- function _ensureLayerOrder() {
838
- if (_layersDeclared)
839
- return;
840
- _layersDeclared = true;
841
- // 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
842
847
  if (document.head.querySelector('style[data-fly-layers]'))
843
848
  return;
844
849
  const style = document.createElement('style');
845
850
  style.setAttribute('data-fly-layers', '');
846
851
  style.textContent = '@layer reset, designsystem, shell, remote, overrides;';
852
+ if (_cspNonce)
853
+ style.nonce = _cspNonce;
847
854
  // Prepend so this always precedes any other layer-bearing <style> blocks.
848
855
  document.head.insertBefore(style, document.head.firstChild);
849
- }
856
+ })();
857
+ // ---------------------------------------------------------------------------
858
+ // Internal helpers
859
+ // ---------------------------------------------------------------------------
850
860
  /**
851
861
  * Discovers the hashed stylesheet href from the remote's `index.html`.
852
- * 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.
853
864
  */
854
- async function _discoverStylesheetHref(remoteBaseUrl) {
865
+ async function _discoverStylesheetHref(appId, remoteBaseUrl) {
855
866
  const indexUrl = remoteBaseUrl.replace(/\/$/, '') + '/index.html';
867
+ const controller = new AbortController();
868
+ const timeoutId = setTimeout(() => controller.abort(), 5000);
856
869
  try {
857
870
  const res = await fetch(indexUrl, {
858
871
  credentials: 'omit',
859
872
  cache: 'default',
873
+ signal: controller.signal,
860
874
  });
875
+ clearTimeout(timeoutId);
861
876
  if (!res.ok)
862
877
  return null;
863
878
  const html = await res.text();
@@ -866,87 +881,163 @@ async function _discoverStylesheetHref(remoteBaseUrl) {
866
881
  const link = doc.head.querySelector('link[rel="stylesheet"]');
867
882
  if (!link?.href)
868
883
  return null;
869
- // `link.href` from DOMParser is resolved relative to the parser's base,
870
- // which defaults to `about:blank`. We get the raw `href` attribute instead.
884
+ // `link.href` from DOMParser resolves against `about:blank`. Use raw attribute.
871
885
  const rawHref = link.getAttribute('href') ?? '';
872
886
  if (!rawHref)
873
887
  return null;
874
- // If the remote emits an absolute URL, use it as-is; otherwise resolve
875
- // 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;
876
900
  if (rawHref.startsWith('http://') || rawHref.startsWith('https://') || rawHref.startsWith('//')) {
877
- return rawHref;
901
+ resolvedHref = rawHref;
902
+ }
903
+ else {
904
+ resolvedHref = remoteBaseUrl.replace(/\/$/, '') + '/' + rawHref.replace(/^\//, '');
878
905
  }
879
- return remoteBaseUrl.replace(/\/$/, '') + '/' + rawHref.replace(/^\//, '');
906
+ return _validateHref(resolvedHref, remoteBaseUrl);
880
907
  }
881
908
  catch {
909
+ clearTimeout(timeoutId);
910
+ // On abort or network error, clear in-flight so a retry is possible.
911
+ _inFlight.delete(appId);
882
912
  return null;
883
913
  }
884
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;
947
+ }
948
+ }
949
+ catch {
950
+ console.warn(`[FlyOS] loadRemoteStyles: could not parse href origin for "${href}"`);
951
+ return null;
952
+ }
953
+ return href;
954
+ }
955
+ // ---------------------------------------------------------------------------
956
+ // Public API
957
+ // ---------------------------------------------------------------------------
885
958
  /**
886
959
  * Injects the remote's stylesheet into `document.head`.
887
960
  *
888
- * @param appId - Stable identifier for the remote app (matches `DesktopApp.id`).
961
+ * @param appId - Stable identifier for the remote app (matches `DesktopApp.id`).
889
962
  * @param remoteBaseUrl - Base URL of the remote, e.g. `https://circles.example.com`
890
963
  * or `http://localhost:7202`. Must NOT include `/remoteEntry.json`.
891
964
  *
892
965
  * The call is idempotent:
893
- * - If a `<style data-fly-app="appId">` whose content references the same href
894
- * 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.
895
968
  * - If the href differs (hot upgrade) → existing element is replaced.
896
969
  * - If no stylesheet is found in `index.html` → no-op (logged as a warning).
897
970
  *
898
- * 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):
899
976
  * <style data-fly-app="<appId>" data-fly-href="<href>">
900
977
  * @import url("<href>") layer(remote);
901
978
  * </style>
902
979
  *
903
980
  * The `data-fly-href` attribute stores the discovered href separately from the
904
- * 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.
905
982
  */
906
983
  async function loadRemoteStyles(appId, remoteBaseUrl) {
907
984
  if (typeof document === 'undefined')
908
985
  return; // SSR guard
909
- // Ensure the canonical layer order is declared before any remote layer is injected.
910
- _ensureLayerOrder();
911
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.
912
990
  let fetchPromise = _inFlight.get(appId);
913
991
  if (!fetchPromise) {
914
- if (_resolvedHref.has(appId)) {
915
- // Already resolved in a previous call — skip the fetch.
916
- fetchPromise = Promise.resolve(_resolvedHref.get(appId) ?? null);
917
- }
918
- else {
919
- fetchPromise = _discoverStylesheetHref(remoteBaseUrl);
920
- _inFlight.set(appId, fetchPromise);
921
- }
992
+ fetchPromise = _discoverStylesheetHref(appId, remoteBaseUrl);
993
+ _inFlight.set(appId, fetchPromise);
922
994
  }
923
995
  const href = await fetchPromise;
924
996
  _inFlight.delete(appId);
925
- _resolvedHref.set(appId, href);
926
997
  if (!href) {
927
998
  console.warn(`[FlyOS] loadRemoteStyles: no stylesheet found in ${remoteBaseUrl}/index.html for appId="${appId}"`);
928
999
  return;
929
1000
  }
930
- const selector = `style[data-fly-app="${CSS.escape(appId)}"]`;
931
- 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}"]`);
932
1004
  if (existing) {
933
1005
  if (existing.getAttribute('data-fly-href') === href)
934
1006
  return; // identical — no-op
935
- // Hot upgrade: replace the entire element so the @import URL updates atomically.
1007
+ // Hot upgrade: remove old element (could be <link> or <style>) atomically.
936
1008
  existing.remove();
937
1009
  }
938
- // CSS Cascade Level 5: `@import url("…") layer(remote)` is the only valid
939
- // way to place an @import inside a named cascade layer. Block-form
940
- // `@layer remote { @import }` is invalid and silently dropped by browsers.
941
- // See module JSDoc for full rationale.
942
- const style = document.createElement('style');
943
- style.setAttribute('data-fly-app', appId);
944
- style.setAttribute('data-fly-href', href);
945
- style.textContent = `@import url("${href}") layer(remote);`;
946
- 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
+ }
947
1037
  }
948
1038
  /**
949
- * 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.
950
1041
  *
951
1042
  * NOTE: Prefer NOT calling this on normal window close — keeping styles loaded
952
1043
  * prevents FOUC flicker when the user reopens the same app. Call only if you
@@ -955,9 +1046,17 @@ async function loadRemoteStyles(appId, remoteBaseUrl) {
955
1046
  function unloadRemoteStyles(appId) {
956
1047
  if (typeof document === 'undefined')
957
1048
  return; // SSR guard
958
- const style = document.head.querySelector(`style[data-fly-app="${CSS.escape(appId)}"]`);
959
- style?.parentNode?.removeChild(style);
960
- _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);
961
1060
  }
962
1061
 
963
1062
  /**
@@ -2770,7 +2869,7 @@ class FlyImageUploadComponent {
2770
2869
  </div>
2771
2870
  }
2772
2871
  </div>
2773
- `, 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 });
2774
2873
  }
2775
2874
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyImageUploadComponent, decorators: [{
2776
2875
  type: Component,
@@ -2824,7 +2923,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
2824
2923
  </div>
2825
2924
  }
2826
2925
  </div>
2827
- `, 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"] }]
2828
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 }] }] } });
2829
2928
 
2830
2929
  const FILE_ICONS = {