@flyos/design-system 3.4.0 → 3.5.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.
@@ -47,7 +47,7 @@ import { CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
47
47
  // tools/publish-library.ps1 at bump time, and asserted by the spec beside this file.
48
48
  // Used only for the diagnostic message; the duplicate-instance detection itself is
49
49
  // version-agnostic, so a stale literal misnames a fork rather than hiding one.
50
- const FLY_DS_VERSION = '3.4.0';
50
+ const FLY_DS_VERSION = '3.5.0';
51
51
  const FLY_DS_REGISTRY_KEY = '__FLY_DS_INSTANCES__';
52
52
  /**
53
53
  * Records this design-system instance on the shared `scope` and returns the
@@ -1218,12 +1218,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1218
1218
  * Standalone-dev route guard: if there is no authenticated session, start the PKCE login (redirects
1219
1219
  * to the STS) and block activation. Not reached in federated mode — the shell mounts the app's root
1220
1220
  * component directly rather than routing through the app shell.
1221
+ *
1222
+ * Before redirecting it stashes the attempted URL under the same appId-scoped sessionStorage key the
1223
+ * step-up flow uses, so the auth callback lands back on the deep link instead of `/` — an
1224
+ * unauthenticated deep link previously lost its target on the STS round-trip.
1221
1225
  */
1222
- const flyStandaloneAuthGuard = () => {
1226
+ const flyStandaloneAuthGuard = (_route, state) => {
1223
1227
  const auth = inject(FlyStandaloneAuthService);
1224
1228
  if (auth.isAuthenticated())
1225
1229
  return true;
1226
- void auth.startLogin();
1230
+ const appId = inject(FLY_STANDALONE_AUTH_CONFIG).appId;
1231
+ try {
1232
+ sessionStorage.setItem(stepUpReturnKey(appId), state.url);
1233
+ }
1234
+ catch {
1235
+ /* sessionStorage unavailable — fall back to the callback's default landing */
1236
+ }
1237
+ auth.startLogin().catch((err) => {
1238
+ // Surface the failure instead of leaving a silent white screen: the guard already returned
1239
+ // false, so if the authorize redirect never happens the router renders nothing at all.
1240
+ console.error('[FlyStandaloneAuth] startLogin failed — the app cannot authenticate', err);
1241
+ });
1227
1242
  return false;
1228
1243
  };
1229
1244
 
@@ -1303,7 +1318,7 @@ function flyStandaloneAuthInterceptor(req, next) {
1303
1318
  * is a safe default that an integrator can override without coordination.
1304
1319
  *
1305
1320
  * Scope: only the keys the **shippable** DS components reference today
1306
- * (`common.*` toolbar/link/emoji-picker labels + `agent.lookup.*` + `select.*` + `typeahead.*` + `cron.*` + `gantt.*` + `form.*` + `captcha.*` + `comment.*` + `canvas_board.*` + `moderation.*` + `people_picker.*` + `currency_selector.*` + `pagination.*` + `tree_nav.*` + `magic_actions.*`). When a new DS component
1321
+ * (`common.*` toolbar/link/emoji-picker labels + `agent.lookup.*` + `select.*` + `typeahead.*` + `cron.*` + `gantt.*` + `form.*` + `captcha.*` + `comment.*` + `canvas_board.*` + `moderation.*` + `people_picker.*` + `strategy_selector.*` + `currency_selector.*` + `pagination.*` + `tree_nav.*` + `magic_actions.*`). When a new DS component
1307
1322
  * starts using `| translate` **or `I18nService.t()`**, add its keys here so it stays
1308
1323
  * self-sufficient — `fly-magic-actions` shipped resolving three keys that existed only in the
1309
1324
  * shell bundle, so an External App rendering it got the raw key string as every disabled
@@ -1575,6 +1590,9 @@ const DS_BASELINE_LOCALES = {
1575
1590
  'captcha.verified': 'Verified',
1576
1591
  'captcha.expired': 'Verification expired — please retry.',
1577
1592
  'captcha.error': 'Verification failed.',
1593
+ // file upload (fly-file-upload)
1594
+ 'files.fileUpload.dropOrClick': 'Drop files or click to browse',
1595
+ 'files.fileUpload.limitHint': 'Max {{n}} files, {{mb}}MB each',
1578
1596
  // standalone auth callback (provideFlyStandaloneAuth)
1579
1597
  'auth.callback.signing_in': 'Signing you in…',
1580
1598
  // comment-thread (fly-comment-thread)
@@ -1641,6 +1659,13 @@ const DS_BASELINE_LOCALES = {
1641
1659
  'people_picker.search_placeholder': 'Search people…',
1642
1660
  'people_picker.remove': 'Remove {{name}}',
1643
1661
  'people_picker.change': 'Change',
1662
+ // strategy selector (fly-strategy-selector)
1663
+ 'strategy_selector.search_placeholder': 'Search strategic objectives…',
1664
+ 'strategy_selector.remove': 'Remove {{name}}',
1665
+ 'strategy_selector.no_results': 'No matching objectives.',
1666
+ 'strategy_selector.loading': 'Loading objectives…',
1667
+ 'strategy_selector.error': 'Objectives could not be loaded.',
1668
+ 'strategy_selector.retry': 'Retry',
1644
1669
  // currency selector (fly-currency-selector)
1645
1670
  'currency_selector.placeholder': 'Select a currency',
1646
1671
  'currency_selector.placeholder_multi': 'Select currencies',
@@ -1968,6 +1993,9 @@ const DS_BASELINE_LOCALES = {
1968
1993
  'captcha.verified': 'تم التحقق',
1969
1994
  'captcha.expired': 'انتهت صلاحية التحقق — يرجى إعادة المحاولة.',
1970
1995
  'captcha.error': 'فشل التحقق.',
1996
+ // file upload (fly-file-upload)
1997
+ 'files.fileUpload.dropOrClick': 'أسقط الملفات أو انقر للتصفح',
1998
+ 'files.fileUpload.limitHint': 'بحد أقصى {{n}} ملفات، {{mb}} ميغابايت لكل ملف',
1971
1999
  // standalone auth callback (provideFlyStandaloneAuth)
1972
2000
  'auth.callback.signing_in': 'جارٍ تسجيل الدخول…',
1973
2001
  // comment-thread (fly-comment-thread)
@@ -2034,6 +2062,13 @@ const DS_BASELINE_LOCALES = {
2034
2062
  'people_picker.search_placeholder': 'ابحث عن أشخاص…',
2035
2063
  'people_picker.remove': 'إزالة {{name}}',
2036
2064
  'people_picker.change': 'تغيير',
2065
+ // strategy selector (fly-strategy-selector)
2066
+ 'strategy_selector.search_placeholder': 'ابحث عن الأهداف الاستراتيجية…',
2067
+ 'strategy_selector.remove': 'إزالة {{name}}',
2068
+ 'strategy_selector.no_results': 'لا توجد أهداف مطابقة.',
2069
+ 'strategy_selector.loading': 'جارٍ تحميل الأهداف…',
2070
+ 'strategy_selector.error': 'تعذّر تحميل الأهداف.',
2071
+ 'strategy_selector.retry': 'إعادة المحاولة',
2037
2072
  // currency selector (fly-currency-selector)
2038
2073
  'currency_selector.placeholder': 'اختر عملة',
2039
2074
  'currency_selector.placeholder_multi': 'اختر العملات',
@@ -2361,6 +2396,9 @@ const DS_BASELINE_LOCALES = {
2361
2396
  'captcha.verified': 'Vérifié',
2362
2397
  'captcha.expired': 'Vérification expirée — veuillez réessayer.',
2363
2398
  'captcha.error': 'Échec de la vérification.',
2399
+ // file upload (fly-file-upload)
2400
+ 'files.fileUpload.dropOrClick': 'Déposez des fichiers ou cliquez pour parcourir',
2401
+ 'files.fileUpload.limitHint': '{{n}} fichiers max., {{mb}} Mo chacun',
2364
2402
  // standalone auth callback (provideFlyStandaloneAuth)
2365
2403
  'auth.callback.signing_in': 'Connexion en cours…',
2366
2404
  // comment-thread (fly-comment-thread)
@@ -2427,6 +2465,13 @@ const DS_BASELINE_LOCALES = {
2427
2465
  'people_picker.search_placeholder': 'Rechercher des personnes…',
2428
2466
  'people_picker.remove': 'Retirer {{name}}',
2429
2467
  'people_picker.change': 'Changer',
2468
+ // strategy selector (fly-strategy-selector)
2469
+ 'strategy_selector.search_placeholder': 'Rechercher des objectifs stratégiques…',
2470
+ 'strategy_selector.remove': 'Retirer {{name}}',
2471
+ 'strategy_selector.no_results': 'Aucun objectif correspondant.',
2472
+ 'strategy_selector.loading': 'Chargement des objectifs…',
2473
+ 'strategy_selector.error': 'Impossible de charger les objectifs.',
2474
+ 'strategy_selector.retry': 'Réessayer',
2430
2475
  // currency selector (fly-currency-selector)
2431
2476
  'currency_selector.placeholder': 'Sélectionner une devise',
2432
2477
  'currency_selector.placeholder_multi': 'Sélectionner des devises',
@@ -2753,6 +2798,9 @@ const DS_BASELINE_LOCALES = {
2753
2798
  'captcha.verified': 'تصدیق ہو گئی',
2754
2799
  'captcha.expired': 'تصدیق کی میعاد ختم — دوبارہ کوشش کریں۔',
2755
2800
  'captcha.error': 'تصدیق ناکام۔',
2801
+ // file upload (fly-file-upload)
2802
+ 'files.fileUpload.dropOrClick': 'فائلیں چھوڑیں یا براؤز کرنے کے لیے کلک کریں',
2803
+ 'files.fileUpload.limitHint': 'زیادہ سے زیادہ {{n}} فائلیں، ہر ایک {{mb}} ایم بی',
2756
2804
  // standalone auth callback (provideFlyStandaloneAuth)
2757
2805
  'auth.callback.signing_in': 'سائن ان ہو رہا ہے…',
2758
2806
  // comment-thread (fly-comment-thread)
@@ -2819,6 +2867,13 @@ const DS_BASELINE_LOCALES = {
2819
2867
  'people_picker.search_placeholder': 'لوگ تلاش کریں…',
2820
2868
  'people_picker.remove': '{{name}} کو ہٹائیں',
2821
2869
  'people_picker.change': 'تبدیل کریں',
2870
+ // strategy selector (fly-strategy-selector)
2871
+ 'strategy_selector.search_placeholder': 'اسٹریٹجک مقاصد تلاش کریں…',
2872
+ 'strategy_selector.remove': '{{name}} کو ہٹائیں',
2873
+ 'strategy_selector.no_results': 'کوئی مماثل مقصد نہیں۔',
2874
+ 'strategy_selector.loading': 'مقاصد لوڈ ہو رہے ہیں…',
2875
+ 'strategy_selector.error': 'مقاصد لوڈ نہیں ہو سکے۔',
2876
+ 'strategy_selector.retry': 'دوبارہ کوشش کریں',
2822
2877
  // currency selector (fly-currency-selector)
2823
2878
  'currency_selector.placeholder': 'کرنسی منتخب کریں',
2824
2879
  'currency_selector.placeholder_multi': 'کرنسیاں منتخب کریں',
@@ -11074,10 +11129,10 @@ class FlyFileUploadComponent {
11074
11129
  const current = this.files().length + this.slots().filter(s => s.status === 'uploading').length;
11075
11130
  return current < this.maxFiles();
11076
11131
  }, ...(ngDevMode ? [{ debugName: "canAddMore" }] : /* istanbul ignore next */ []));
11077
- limitHint = computed(() => {
11078
- const mb = this.maxFileSizeBytes() / (1024 * 1024);
11079
- return `Max ${this.maxFiles()} files, ${mb}MB each`;
11080
- }, ...(ngDevMode ? [{ debugName: "limitHint" }] : /* istanbul ignore next */ []));
11132
+ limitHintParams = computed(() => ({
11133
+ n: this.maxFiles(),
11134
+ mb: this.maxFileSizeBytes() / (1024 * 1024),
11135
+ }), ...(ngDevMode ? [{ debugName: "limitHintParams" }] : /* istanbul ignore next */ []));
11081
11136
  triggerFileInput() {
11082
11137
  this.fileInput()?.nativeElement.click();
11083
11138
  }
@@ -11178,11 +11233,11 @@ class FlyFileUploadComponent {
11178
11233
  });
11179
11234
  }
11180
11235
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyFileUploadComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11181
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyFileUploadComponent, isStandalone: true, selector: "fly-file-upload", inputs: { maxFiles: { classPropertyName: "maxFiles", publicName: "maxFiles", isSignal: true, isRequired: false, transformFunction: null }, maxFileSizeBytes: { classPropertyName: "maxFileSizeBytes", publicName: "maxFileSizeBytes", isSignal: true, isRequired: false, transformFunction: null }, accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, sourceApp: { classPropertyName: "sourceApp", publicName: "sourceApp", isSignal: true, isRequired: false, transformFunction: null }, sourceEntityType: { classPropertyName: "sourceEntityType", publicName: "sourceEntityType", isSignal: true, isRequired: false, transformFunction: null }, sourceEntityId: { classPropertyName: "sourceEntityId", publicName: "sourceEntityId", isSignal: true, isRequired: false, transformFunction: null }, existingFileIds: { classPropertyName: "existingFileIds", publicName: "existingFileIds", isSignal: true, isRequired: false, transformFunction: null }, files: { classPropertyName: "files", publicName: "files", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { files: "filesChange", filesChanged: "filesChanged" }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"fly-file-upload\">\r\n @if (canAddMore()) {\r\n <button type=\"button\" class=\"fly-file-upload__dropzone\"\r\n [class.fly-file-upload__dropzone--drag]=\"dragging()\"\r\n (click)=\"triggerFileInput()\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onDrop($event)\">\r\n <span class=\"pi pi-cloud-upload fly-file-upload__icon\" aria-hidden=\"true\"></span>\r\n <span class=\"fly-file-upload__label\">{{ 'files.fileUpload.dropOrClick' | translate }}</span>\r\n <span class=\"fly-file-upload__hint\">{{ limitHint() }}</span>\r\n </button>\r\n }\r\n\r\n @if (error()) {\r\n <div class=\"fly-file-upload__error\">{{ error() }}</div>\r\n }\r\n\r\n @if (allSlots().length) {\r\n <ul class=\"fly-file-upload__list\">\r\n @for (slot of allSlots(); track slot.file.name + slot.file.size) {\r\n <li class=\"fly-file-upload__item\">\r\n <span class=\"pi {{ iconFor(slot) }} fly-file-upload__file-icon\" aria-hidden=\"true\"></span>\r\n <div class=\"fly-file-upload__file-info\">\r\n <span class=\"fly-file-upload__file-name\">{{ slot.file.name }}</span>\r\n <span class=\"fly-file-upload__file-size\">{{ formatFileSize(slot.file.size) }}</span>\r\n </div>\r\n @if (slot.status === 'uploading') {\r\n <div class=\"fly-file-upload__progress-track\">\r\n <div class=\"fly-file-upload__progress-fill\" [style.width.%]=\"slot.progress\"></div>\r\n </div>\r\n }\r\n @if (slot.status === 'error') {\r\n <span class=\"fly-file-upload__file-error\" [title]=\"slot.error ?? ''\">\r\n <span class=\"pi pi-exclamation-triangle\" aria-hidden=\"true\"></span>\r\n </span>\r\n }\r\n @if (slot.status === 'done') {\r\n <span class=\"pi pi-check-circle fly-file-upload__file-ok\" aria-hidden=\"true\"></span>\r\n }\r\n <button type=\"button\" class=\"fly-file-upload__remove-btn\" (click)=\"removeSlot(slot)\" [title]=\"'common.remove' | translate\">\r\n <span class=\"pi pi-times\" aria-hidden=\"true\"></span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n\r\n @if (existingFiles().length && !allSlots().length) {\r\n <ul class=\"fly-file-upload__list\">\r\n @for (f of existingFiles(); track f.id) {\r\n <li class=\"fly-file-upload__item\">\r\n <span class=\"pi {{ iconForInfo(f) }} fly-file-upload__file-icon\" aria-hidden=\"true\"></span>\r\n <div class=\"fly-file-upload__file-info\">\r\n <span class=\"fly-file-upload__file-name\">{{ f.fileName }}</span>\r\n <span class=\"fly-file-upload__file-size\">{{ formatFileSize(f.sizeBytes) }}</span>\r\n </div>\r\n <span class=\"pi pi-check-circle fly-file-upload__file-ok\" aria-hidden=\"true\"></span>\r\n <button type=\"button\" class=\"fly-file-upload__remove-btn\" (click)=\"removeExisting(f)\" [title]=\"'common.remove' | translate\">\r\n <span class=\"pi pi-times\" aria-hidden=\"true\"></span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n\r\n <input #fileInput type=\"file\" [accept]=\"accept()\" multiple class=\"fly-file-upload__hidden\" (change)=\"onFilesSelected($event)\" />\r\n</div>\r\n", styles: [".fly-file-upload{display:flex;flex-direction:column;gap:8px}.fly-file-upload__hidden{display:none}.fly-file-upload__dropzone{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;padding:20px;border:2px dashed var(--separator-primary, #e5e7eb);border-radius:10px;background:var(--fill-quaternary, #f9fafb);cursor:pointer;transition:border-color .15s,background .15s}.fly-file-upload__dropzone:hover,.fly-file-upload__dropzone--drag{border-color:var(--accent-primary, #0071e3);background:var(--fill-tertiary, #f3f4f6)}.fly-file-upload__icon{font-size:24px;color:var(--label-tertiary, #9ca3af)}.fly-file-upload__label{font-size:13px;color:var(--label-secondary, #6b7280)}.fly-file-upload__hint{font-size:11px;color:var(--label-tertiary, #9ca3af)}.fly-file-upload__error{font-size:12px;color:var(--system-red, #ef4444);padding:4px 0}.fly-file-upload__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:4px}.fly-file-upload__item{display:flex;align-items:center;gap:10px;padding:8px 12px;border:1px solid var(--separator-primary, #e5e7eb);border-radius:8px;background:var(--fill-quaternary, #f9fafb);font-size:13px}.fly-file-upload__file-icon{font-size:18px;color:var(--label-tertiary, #9ca3af);flex-shrink:0}.fly-file-upload__file-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.fly-file-upload__file-name{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--label-primary, #1f2937)}.fly-file-upload__file-size{font-size:11px;color:var(--label-tertiary, #9ca3af)}.fly-file-upload__progress-track{width:60px;height:4px;border-radius:2px;background:var(--fill-tertiary, #e5e7eb);overflow:hidden}.fly-file-upload__progress-fill{height:100%;background:var(--accent-primary, #0071e3);border-radius:2px;transition:width .2s}.fly-file-upload__file-ok{color:var(--system-green, #22c55e);font-size:14px}.fly-file-upload__file-error{color:var(--system-red, #ef4444);font-size:14px}.fly-file-upload__remove-btn{background:none;border:none;cursor:pointer;color:var(--label-tertiary, #9ca3af);width:24px;height:24px;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:color .15s,background .15s;flex-shrink:0}.fly-file-upload__remove-btn:hover{color:var(--system-red, #ef4444);background:var(--fill-tertiary, #f3f4f6)}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11236
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyFileUploadComponent, isStandalone: true, selector: "fly-file-upload", inputs: { maxFiles: { classPropertyName: "maxFiles", publicName: "maxFiles", isSignal: true, isRequired: false, transformFunction: null }, maxFileSizeBytes: { classPropertyName: "maxFileSizeBytes", publicName: "maxFileSizeBytes", isSignal: true, isRequired: false, transformFunction: null }, accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, sourceApp: { classPropertyName: "sourceApp", publicName: "sourceApp", isSignal: true, isRequired: false, transformFunction: null }, sourceEntityType: { classPropertyName: "sourceEntityType", publicName: "sourceEntityType", isSignal: true, isRequired: false, transformFunction: null }, sourceEntityId: { classPropertyName: "sourceEntityId", publicName: "sourceEntityId", isSignal: true, isRequired: false, transformFunction: null }, existingFileIds: { classPropertyName: "existingFileIds", publicName: "existingFileIds", isSignal: true, isRequired: false, transformFunction: null }, files: { classPropertyName: "files", publicName: "files", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { files: "filesChange", filesChanged: "filesChanged" }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"fly-file-upload\">\r\n @if (canAddMore()) {\r\n <button type=\"button\" class=\"fly-file-upload__dropzone\"\r\n [class.fly-file-upload__dropzone--drag]=\"dragging()\"\r\n (click)=\"triggerFileInput()\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onDrop($event)\">\r\n <span class=\"pi pi-cloud-upload fly-file-upload__icon\" aria-hidden=\"true\"></span>\r\n <span class=\"fly-file-upload__label\">{{ 'files.fileUpload.dropOrClick' | translate }}</span>\r\n <span class=\"fly-file-upload__hint\">{{ 'files.fileUpload.limitHint' | translate: limitHintParams() }}</span>\r\n </button>\r\n }\r\n\r\n @if (error()) {\r\n <div class=\"fly-file-upload__error\">{{ error() }}</div>\r\n }\r\n\r\n @if (allSlots().length) {\r\n <ul class=\"fly-file-upload__list\">\r\n @for (slot of allSlots(); track slot.file.name + slot.file.size) {\r\n <li class=\"fly-file-upload__item\">\r\n <span class=\"pi {{ iconFor(slot) }} fly-file-upload__file-icon\" aria-hidden=\"true\"></span>\r\n <div class=\"fly-file-upload__file-info\">\r\n <span class=\"fly-file-upload__file-name\">{{ slot.file.name }}</span>\r\n <span class=\"fly-file-upload__file-size\">{{ formatFileSize(slot.file.size) }}</span>\r\n </div>\r\n @if (slot.status === 'uploading') {\r\n <div class=\"fly-file-upload__progress-track\">\r\n <div class=\"fly-file-upload__progress-fill\" [style.width.%]=\"slot.progress\"></div>\r\n </div>\r\n }\r\n @if (slot.status === 'error') {\r\n <span class=\"fly-file-upload__file-error\" [title]=\"slot.error ?? ''\">\r\n <span class=\"pi pi-exclamation-triangle\" aria-hidden=\"true\"></span>\r\n </span>\r\n }\r\n @if (slot.status === 'done') {\r\n <span class=\"pi pi-check-circle fly-file-upload__file-ok\" aria-hidden=\"true\"></span>\r\n }\r\n <button type=\"button\" class=\"fly-file-upload__remove-btn\" (click)=\"removeSlot(slot)\" [title]=\"'common.remove' | translate\">\r\n <span class=\"pi pi-times\" aria-hidden=\"true\"></span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n\r\n @if (existingFiles().length && !allSlots().length) {\r\n <ul class=\"fly-file-upload__list\">\r\n @for (f of existingFiles(); track f.id) {\r\n <li class=\"fly-file-upload__item\">\r\n <span class=\"pi {{ iconForInfo(f) }} fly-file-upload__file-icon\" aria-hidden=\"true\"></span>\r\n <div class=\"fly-file-upload__file-info\">\r\n <span class=\"fly-file-upload__file-name\">{{ f.fileName }}</span>\r\n <span class=\"fly-file-upload__file-size\">{{ formatFileSize(f.sizeBytes) }}</span>\r\n </div>\r\n <span class=\"pi pi-check-circle fly-file-upload__file-ok\" aria-hidden=\"true\"></span>\r\n <button type=\"button\" class=\"fly-file-upload__remove-btn\" (click)=\"removeExisting(f)\" [title]=\"'common.remove' | translate\">\r\n <span class=\"pi pi-times\" aria-hidden=\"true\"></span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n\r\n <input #fileInput type=\"file\" [accept]=\"accept()\" multiple class=\"fly-file-upload__hidden\" (change)=\"onFilesSelected($event)\" />\r\n</div>\r\n", styles: [".fly-file-upload{display:flex;flex-direction:column;gap:8px}.fly-file-upload__hidden{display:none}.fly-file-upload__dropzone{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;padding:20px;border:2px dashed var(--line, #e5e7eb);border-radius:10px;background:var(--bg-3, #f9fafb);cursor:pointer;transition:border-color .15s,background .15s}.fly-file-upload__dropzone:hover,.fly-file-upload__dropzone--drag{border-color:var(--accent, #0071e3);background:var(--bg-hover, #f3f4f6)}.fly-file-upload__icon{font-size:24px;color:var(--ink-4, #9ca3af)}.fly-file-upload__label{font-size:13px;color:var(--ink-3, #6b7280)}.fly-file-upload__hint{font-size:11px;color:var(--ink-4, #9ca3af)}.fly-file-upload__error{font-size:12px;color:var(--danger, #ef4444);padding:4px 0}.fly-file-upload__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:4px}.fly-file-upload__item{display:flex;align-items:center;gap:10px;padding:8px 12px;border:1px solid var(--line, #e5e7eb);border-radius:8px;background:var(--bg-3, #f9fafb);font-size:13px}.fly-file-upload__file-icon{font-size:18px;color:var(--ink-4, #9ca3af);flex-shrink:0}.fly-file-upload__file-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.fly-file-upload__file-name{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--ink, #1f2937)}.fly-file-upload__file-size{font-size:11px;color:var(--ink-4, #9ca3af)}.fly-file-upload__progress-track{width:60px;height:4px;border-radius:2px;background:var(--bg-hover, #e5e7eb);overflow:hidden}.fly-file-upload__progress-fill{height:100%;background:var(--accent, #0071e3);border-radius:2px;transition:width .2s}.fly-file-upload__file-ok{color:var(--success, #22c55e);font-size:14px}.fly-file-upload__file-error{color:var(--danger, #ef4444);font-size:14px}.fly-file-upload__remove-btn{background:none;border:none;cursor:pointer;color:var(--ink-4, #9ca3af);width:24px;height:24px;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:color .15s,background .15s;flex-shrink:0}.fly-file-upload__remove-btn:hover{color:var(--danger, #ef4444);background:var(--bg-hover, #f3f4f6)}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11182
11237
  }
11183
11238
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyFileUploadComponent, decorators: [{
11184
11239
  type: Component,
11185
- args: [{ selector: 'fly-file-upload', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fly-file-upload\">\r\n @if (canAddMore()) {\r\n <button type=\"button\" class=\"fly-file-upload__dropzone\"\r\n [class.fly-file-upload__dropzone--drag]=\"dragging()\"\r\n (click)=\"triggerFileInput()\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onDrop($event)\">\r\n <span class=\"pi pi-cloud-upload fly-file-upload__icon\" aria-hidden=\"true\"></span>\r\n <span class=\"fly-file-upload__label\">{{ 'files.fileUpload.dropOrClick' | translate }}</span>\r\n <span class=\"fly-file-upload__hint\">{{ limitHint() }}</span>\r\n </button>\r\n }\r\n\r\n @if (error()) {\r\n <div class=\"fly-file-upload__error\">{{ error() }}</div>\r\n }\r\n\r\n @if (allSlots().length) {\r\n <ul class=\"fly-file-upload__list\">\r\n @for (slot of allSlots(); track slot.file.name + slot.file.size) {\r\n <li class=\"fly-file-upload__item\">\r\n <span class=\"pi {{ iconFor(slot) }} fly-file-upload__file-icon\" aria-hidden=\"true\"></span>\r\n <div class=\"fly-file-upload__file-info\">\r\n <span class=\"fly-file-upload__file-name\">{{ slot.file.name }}</span>\r\n <span class=\"fly-file-upload__file-size\">{{ formatFileSize(slot.file.size) }}</span>\r\n </div>\r\n @if (slot.status === 'uploading') {\r\n <div class=\"fly-file-upload__progress-track\">\r\n <div class=\"fly-file-upload__progress-fill\" [style.width.%]=\"slot.progress\"></div>\r\n </div>\r\n }\r\n @if (slot.status === 'error') {\r\n <span class=\"fly-file-upload__file-error\" [title]=\"slot.error ?? ''\">\r\n <span class=\"pi pi-exclamation-triangle\" aria-hidden=\"true\"></span>\r\n </span>\r\n }\r\n @if (slot.status === 'done') {\r\n <span class=\"pi pi-check-circle fly-file-upload__file-ok\" aria-hidden=\"true\"></span>\r\n }\r\n <button type=\"button\" class=\"fly-file-upload__remove-btn\" (click)=\"removeSlot(slot)\" [title]=\"'common.remove' | translate\">\r\n <span class=\"pi pi-times\" aria-hidden=\"true\"></span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n\r\n @if (existingFiles().length && !allSlots().length) {\r\n <ul class=\"fly-file-upload__list\">\r\n @for (f of existingFiles(); track f.id) {\r\n <li class=\"fly-file-upload__item\">\r\n <span class=\"pi {{ iconForInfo(f) }} fly-file-upload__file-icon\" aria-hidden=\"true\"></span>\r\n <div class=\"fly-file-upload__file-info\">\r\n <span class=\"fly-file-upload__file-name\">{{ f.fileName }}</span>\r\n <span class=\"fly-file-upload__file-size\">{{ formatFileSize(f.sizeBytes) }}</span>\r\n </div>\r\n <span class=\"pi pi-check-circle fly-file-upload__file-ok\" aria-hidden=\"true\"></span>\r\n <button type=\"button\" class=\"fly-file-upload__remove-btn\" (click)=\"removeExisting(f)\" [title]=\"'common.remove' | translate\">\r\n <span class=\"pi pi-times\" aria-hidden=\"true\"></span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n\r\n <input #fileInput type=\"file\" [accept]=\"accept()\" multiple class=\"fly-file-upload__hidden\" (change)=\"onFilesSelected($event)\" />\r\n</div>\r\n", styles: [".fly-file-upload{display:flex;flex-direction:column;gap:8px}.fly-file-upload__hidden{display:none}.fly-file-upload__dropzone{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;padding:20px;border:2px dashed var(--separator-primary, #e5e7eb);border-radius:10px;background:var(--fill-quaternary, #f9fafb);cursor:pointer;transition:border-color .15s,background .15s}.fly-file-upload__dropzone:hover,.fly-file-upload__dropzone--drag{border-color:var(--accent-primary, #0071e3);background:var(--fill-tertiary, #f3f4f6)}.fly-file-upload__icon{font-size:24px;color:var(--label-tertiary, #9ca3af)}.fly-file-upload__label{font-size:13px;color:var(--label-secondary, #6b7280)}.fly-file-upload__hint{font-size:11px;color:var(--label-tertiary, #9ca3af)}.fly-file-upload__error{font-size:12px;color:var(--system-red, #ef4444);padding:4px 0}.fly-file-upload__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:4px}.fly-file-upload__item{display:flex;align-items:center;gap:10px;padding:8px 12px;border:1px solid var(--separator-primary, #e5e7eb);border-radius:8px;background:var(--fill-quaternary, #f9fafb);font-size:13px}.fly-file-upload__file-icon{font-size:18px;color:var(--label-tertiary, #9ca3af);flex-shrink:0}.fly-file-upload__file-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.fly-file-upload__file-name{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--label-primary, #1f2937)}.fly-file-upload__file-size{font-size:11px;color:var(--label-tertiary, #9ca3af)}.fly-file-upload__progress-track{width:60px;height:4px;border-radius:2px;background:var(--fill-tertiary, #e5e7eb);overflow:hidden}.fly-file-upload__progress-fill{height:100%;background:var(--accent-primary, #0071e3);border-radius:2px;transition:width .2s}.fly-file-upload__file-ok{color:var(--system-green, #22c55e);font-size:14px}.fly-file-upload__file-error{color:var(--system-red, #ef4444);font-size:14px}.fly-file-upload__remove-btn{background:none;border:none;cursor:pointer;color:var(--label-tertiary, #9ca3af);width:24px;height:24px;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:color .15s,background .15s;flex-shrink:0}.fly-file-upload__remove-btn:hover{color:var(--system-red, #ef4444);background:var(--fill-tertiary, #f3f4f6)}\n"] }]
11240
+ args: [{ selector: 'fly-file-upload', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fly-file-upload\">\r\n @if (canAddMore()) {\r\n <button type=\"button\" class=\"fly-file-upload__dropzone\"\r\n [class.fly-file-upload__dropzone--drag]=\"dragging()\"\r\n (click)=\"triggerFileInput()\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onDrop($event)\">\r\n <span class=\"pi pi-cloud-upload fly-file-upload__icon\" aria-hidden=\"true\"></span>\r\n <span class=\"fly-file-upload__label\">{{ 'files.fileUpload.dropOrClick' | translate }}</span>\r\n <span class=\"fly-file-upload__hint\">{{ 'files.fileUpload.limitHint' | translate: limitHintParams() }}</span>\r\n </button>\r\n }\r\n\r\n @if (error()) {\r\n <div class=\"fly-file-upload__error\">{{ error() }}</div>\r\n }\r\n\r\n @if (allSlots().length) {\r\n <ul class=\"fly-file-upload__list\">\r\n @for (slot of allSlots(); track slot.file.name + slot.file.size) {\r\n <li class=\"fly-file-upload__item\">\r\n <span class=\"pi {{ iconFor(slot) }} fly-file-upload__file-icon\" aria-hidden=\"true\"></span>\r\n <div class=\"fly-file-upload__file-info\">\r\n <span class=\"fly-file-upload__file-name\">{{ slot.file.name }}</span>\r\n <span class=\"fly-file-upload__file-size\">{{ formatFileSize(slot.file.size) }}</span>\r\n </div>\r\n @if (slot.status === 'uploading') {\r\n <div class=\"fly-file-upload__progress-track\">\r\n <div class=\"fly-file-upload__progress-fill\" [style.width.%]=\"slot.progress\"></div>\r\n </div>\r\n }\r\n @if (slot.status === 'error') {\r\n <span class=\"fly-file-upload__file-error\" [title]=\"slot.error ?? ''\">\r\n <span class=\"pi pi-exclamation-triangle\" aria-hidden=\"true\"></span>\r\n </span>\r\n }\r\n @if (slot.status === 'done') {\r\n <span class=\"pi pi-check-circle fly-file-upload__file-ok\" aria-hidden=\"true\"></span>\r\n }\r\n <button type=\"button\" class=\"fly-file-upload__remove-btn\" (click)=\"removeSlot(slot)\" [title]=\"'common.remove' | translate\">\r\n <span class=\"pi pi-times\" aria-hidden=\"true\"></span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n\r\n @if (existingFiles().length && !allSlots().length) {\r\n <ul class=\"fly-file-upload__list\">\r\n @for (f of existingFiles(); track f.id) {\r\n <li class=\"fly-file-upload__item\">\r\n <span class=\"pi {{ iconForInfo(f) }} fly-file-upload__file-icon\" aria-hidden=\"true\"></span>\r\n <div class=\"fly-file-upload__file-info\">\r\n <span class=\"fly-file-upload__file-name\">{{ f.fileName }}</span>\r\n <span class=\"fly-file-upload__file-size\">{{ formatFileSize(f.sizeBytes) }}</span>\r\n </div>\r\n <span class=\"pi pi-check-circle fly-file-upload__file-ok\" aria-hidden=\"true\"></span>\r\n <button type=\"button\" class=\"fly-file-upload__remove-btn\" (click)=\"removeExisting(f)\" [title]=\"'common.remove' | translate\">\r\n <span class=\"pi pi-times\" aria-hidden=\"true\"></span>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n }\r\n\r\n <input #fileInput type=\"file\" [accept]=\"accept()\" multiple class=\"fly-file-upload__hidden\" (change)=\"onFilesSelected($event)\" />\r\n</div>\r\n", styles: [".fly-file-upload{display:flex;flex-direction:column;gap:8px}.fly-file-upload__hidden{display:none}.fly-file-upload__dropzone{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;padding:20px;border:2px dashed var(--line, #e5e7eb);border-radius:10px;background:var(--bg-3, #f9fafb);cursor:pointer;transition:border-color .15s,background .15s}.fly-file-upload__dropzone:hover,.fly-file-upload__dropzone--drag{border-color:var(--accent, #0071e3);background:var(--bg-hover, #f3f4f6)}.fly-file-upload__icon{font-size:24px;color:var(--ink-4, #9ca3af)}.fly-file-upload__label{font-size:13px;color:var(--ink-3, #6b7280)}.fly-file-upload__hint{font-size:11px;color:var(--ink-4, #9ca3af)}.fly-file-upload__error{font-size:12px;color:var(--danger, #ef4444);padding:4px 0}.fly-file-upload__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:4px}.fly-file-upload__item{display:flex;align-items:center;gap:10px;padding:8px 12px;border:1px solid var(--line, #e5e7eb);border-radius:8px;background:var(--bg-3, #f9fafb);font-size:13px}.fly-file-upload__file-icon{font-size:18px;color:var(--ink-4, #9ca3af);flex-shrink:0}.fly-file-upload__file-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.fly-file-upload__file-name{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--ink, #1f2937)}.fly-file-upload__file-size{font-size:11px;color:var(--ink-4, #9ca3af)}.fly-file-upload__progress-track{width:60px;height:4px;border-radius:2px;background:var(--bg-hover, #e5e7eb);overflow:hidden}.fly-file-upload__progress-fill{height:100%;background:var(--accent, #0071e3);border-radius:2px;transition:width .2s}.fly-file-upload__file-ok{color:var(--success, #22c55e);font-size:14px}.fly-file-upload__file-error{color:var(--danger, #ef4444);font-size:14px}.fly-file-upload__remove-btn{background:none;border:none;cursor:pointer;color:var(--ink-4, #9ca3af);width:24px;height:24px;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:color .15s,background .15s;flex-shrink:0}.fly-file-upload__remove-btn:hover{color:var(--danger, #ef4444);background:var(--bg-hover, #f3f4f6)}\n"] }]
11186
11241
  }], ctorParameters: () => [], propDecorators: { maxFiles: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFiles", required: false }] }], maxFileSizeBytes: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFileSizeBytes", required: false }] }], accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", 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 }] }], existingFileIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "existingFileIds", required: false }] }], files: [{ type: i0.Input, args: [{ isSignal: true, alias: "files", required: false }] }, { type: i0.Output, args: ["filesChange"] }], filesChanged: [{ type: i0.Output, args: ["filesChanged"] }], fileInput: [{ type: i0.ViewChild, args: ['fileInput', { isSignal: true }] }] } });
11187
11242
 
11188
11243
  /** Process-wide counter so every instance owns unique element ids (`aria-controls`,
@@ -19615,6 +19670,294 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
19615
19670
  args: [FlyTypeaheadComponent]
19616
19671
  }] } });
19617
19672
 
19673
+ /** The platform endpoint the component falls back to. Relative — the host's gateway + auth interceptors apply. */
19674
+ const FLY_STRATEGY_OBJECTIVES_LOOKUP_ENDPOINT = '/api/strategies/objectives/lookup';
19675
+ /**
19676
+ * Tolerant unwrap of the `ApiResponse<List<ObjectiveLookupDto>>` envelope. Anything that
19677
+ * doesn't parse degrades to an empty list — a picker must degrade to "no options", never throw.
19678
+ */
19679
+ function flyUnwrapObjectiveLookup(res) {
19680
+ const data = res?.data;
19681
+ if (!Array.isArray(data))
19682
+ return [];
19683
+ return data.filter((r) => !!r && typeof r.id === 'string' &&
19684
+ typeof r.title === 'string');
19685
+ }
19686
+ /**
19687
+ * **`fly-strategy-selector`** — pick ONE strategic objective from the Strategies
19688
+ * app, so any Core or External App can optionally link its own entity (a
19689
+ * programme, an initiative, a budget line) to the strategy it serves.
19690
+ *
19691
+ * Pre-canned for the Strategies app's cross-strategy objective lookup
19692
+ * (`GET /api/strategies/objectives/lookup?q=`, `ObjectiveLookupDto` — only
19693
+ * StrategicObjective items on the PUBLISHED version of an Active strategy
19694
+ * surface there): with no data inputs it queries that endpoint itself through
19695
+ * the host's `HttpClient`, so gateway routing and the auth interceptor apply
19696
+ * and a consuming app writes one tag and nothing else. A host that must not
19697
+ * reach the platform directly supplies `[searchFn]` instead — the same
19698
+ * transport-agnostic escape hatch `fly-people-picker` and
19699
+ * `fly-currency-selector` offer.
19700
+ *
19701
+ * A bespoke panel (own search box + floating results box), not a wrapped
19702
+ * `fly-typeahead` — a flat list can't show which strategy an objective belongs
19703
+ * to, and two strategies routinely share near-identical objective titles.
19704
+ * Results are grouped into contiguous {@link FlyStrategyObjectiveGroup} runs
19705
+ * with the parent strategy's own icon as the group header, mirroring
19706
+ * `fly-currency-selector`'s pinned/all group pattern.
19707
+ *
19708
+ * Single-select by design (an entity serves ONE objective link here; a many-
19709
+ * to-many mapping is a matrix surface, not a form field) and built for
19710
+ * OPTIONAL fields: the empty state is a search box, a pick renders as a
19711
+ * removable chip (objective title + parent strategy secondary line), and
19712
+ * {@link selectionChange} emits `null` on removal.
19713
+ *
19714
+ * Emits the full {@link FlyStrategyObjectiveRef}, not just the id: the
19715
+ * consumer freezes the label at link time (the cross-app reference canon —
19716
+ * the link must stay renderable even when the strategy is later archived or
19717
+ * the caller can no longer read it).
19718
+ *
19719
+ * For an edit surface, feed {@link initialSelection} with the saved ref (the
19720
+ * consumer's own frozen copy — no re-resolve round-trip needed). Seeding never
19721
+ * emits {@link selectionChange}, so loading a form does not mark it dirty.
19722
+ *
19723
+ * i18n is self-sufficient through the `strategy_selector.*` keys in
19724
+ * `DS_BASELINE_LOCALES` (en/ar/fr/ur). RTL works via logical CSS.
19725
+ *
19726
+ * @example
19727
+ * ```html
19728
+ * <!-- Queries /api/strategies/objectives/lookup itself: -->
19729
+ * <fly-strategy-selector (selectionChange)="onObjectivePicked($event)" />
19730
+ *
19731
+ * <!-- Edit surface, seeded from the host's frozen ref: -->
19732
+ * <fly-strategy-selector
19733
+ * [initialSelection]="savedObjectiveRef()"
19734
+ * (selectionChange)="onObjectiveChanged($event)" />
19735
+ * ```
19736
+ */
19737
+ class FlyStrategySelectorComponent {
19738
+ http = inject(HttpClient, { optional: true });
19739
+ // ── Inputs ───────────────────────────────────────────────────────────────────
19740
+ /** Host-supplied loader. Omit to fall back to `GET /api/strategies/objectives/lookup`. */
19741
+ searchFn = input(null, ...(ngDevMode ? [{ debugName: "searchFn" }] : /* istanbul ignore next */ []));
19742
+ /** Saved ref to seed an edit surface with — rendered as the picked chip. Seeded ONCE, on the
19743
+ * first non-null value (a host's load typically lands after construction); later input changes
19744
+ * are ignored so a re-emitting load can't clobber the user's in-progress edit. Seeding does NOT
19745
+ * emit {@link selectionChange}. */
19746
+ initialSelection = input(null, ...(ngDevMode ? [{ debugName: "initialSelection" }] : /* istanbul ignore next */ []));
19747
+ /** Placeholder for the search box. Falls back to the localized `strategy_selector.search_placeholder`. */
19748
+ placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
19749
+ /** Debounce (ms) between the last keystroke and the search call. */
19750
+ debounceMs = input(250, ...(ngDevMode ? [{ debugName: "debounceMs" }] : /* istanbul ignore next */ []));
19751
+ // ── Outputs ──────────────────────────────────────────────────────────────────
19752
+ /** The picked objective in full (label freezable by the host), or `null` when removed. */
19753
+ selectionChange = output();
19754
+ searchEl;
19755
+ host = inject((ElementRef));
19756
+ // ── State ────────────────────────────────────────────────────────────────────
19757
+ _selected = signal(null, ...(ngDevMode ? [{ debugName: "_selected" }] : /* istanbul ignore next */ []));
19758
+ isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
19759
+ searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : /* istanbul ignore next */ []));
19760
+ activeIndex = signal(0, ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
19761
+ loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
19762
+ loadFailed = signal(false, ...(ngDevMode ? [{ debugName: "loadFailed" }] : /* istanbul ignore next */ []));
19763
+ /** The most recent search batch, already ordered strategy-first by the server/mock. */
19764
+ _results = signal([], ...(ngDevMode ? [{ debugName: "_results" }] : /* istanbul ignore next */ []));
19765
+ _searchStarted = false;
19766
+ /** One-shot latch: flips true after {@link initialSelection} has seeded the chip. */
19767
+ _seeded = false;
19768
+ _timer = null;
19769
+ _searchSub = null;
19770
+ constructor() {
19771
+ effect(() => {
19772
+ const init = this.initialSelection();
19773
+ if (this._seeded || init === null)
19774
+ return;
19775
+ this._seeded = true;
19776
+ this._selected.set(init);
19777
+ });
19778
+ }
19779
+ selected = this._selected.asReadonly();
19780
+ showSearch = computed(() => this._selected() === null, ...(ngDevMode ? [{ debugName: "showSearch" }] : /* istanbul ignore next */ []));
19781
+ /** {@link _results} folded into contiguous per-strategy runs — the panel's drilldown grouping.
19782
+ * Also doubles as the flat keyboard-navigation order (a group header is never itself an option,
19783
+ * so nav index N always resolves via {@link navOptions}, not a per-group offset). */
19784
+ groups = computed(() => {
19785
+ const rows = this._results();
19786
+ const out = [];
19787
+ for (const r of rows) {
19788
+ const last = out[out.length - 1];
19789
+ if (last && last.strategyId === r.strategyId)
19790
+ last.items.push(r);
19791
+ else {
19792
+ out.push({
19793
+ strategyId: r.strategyId,
19794
+ strategyTitle: r.strategyTitle,
19795
+ strategyIcon: r.strategyIcon ?? null,
19796
+ items: [r],
19797
+ });
19798
+ }
19799
+ }
19800
+ return out;
19801
+ }, ...(ngDevMode ? [{ debugName: "groups" }] : /* istanbul ignore next */ []));
19802
+ navOptions = computed(() => this._results(), ...(ngDevMode ? [{ debugName: "navOptions" }] : /* istanbul ignore next */ []));
19803
+ navIndexOf(id) {
19804
+ return this.navOptions().findIndex((r) => r.id === id);
19805
+ }
19806
+ activeDescendant = computed(() => {
19807
+ const options = this.navOptions();
19808
+ if (!this.isOpen() || options.length === 0)
19809
+ return '';
19810
+ return this.optionId(Math.min(this.activeIndex(), options.length - 1));
19811
+ }, ...(ngDevMode ? [{ debugName: "activeDescendant" }] : /* istanbul ignore next */ []));
19812
+ optionId(index) {
19813
+ return `fly-strategy-selector-opt-${index}`;
19814
+ }
19815
+ // ── Open / close ───────────────────────────────────────────────────────────
19816
+ open() {
19817
+ if (this.isOpen())
19818
+ return;
19819
+ this.isOpen.set(true);
19820
+ this.activeIndex.set(0);
19821
+ // Surface the "top N" once per mount; don't re-hit the server on every refocus.
19822
+ if (!this._searchStarted)
19823
+ this._runSearch(this.searchTerm());
19824
+ queueMicrotask(() => this.searchEl?.nativeElement.focus());
19825
+ }
19826
+ close() {
19827
+ if (!this.isOpen())
19828
+ return;
19829
+ this.isOpen.set(false);
19830
+ }
19831
+ onSearchInput(value) {
19832
+ this.searchTerm.set(value);
19833
+ this.activeIndex.set(0);
19834
+ this.isOpen.set(true);
19835
+ this._scheduleSearch(value.trim());
19836
+ }
19837
+ // ── Keyboard ───────────────────────────────────────────────────────────────
19838
+ onPanelKeydown(event) {
19839
+ const options = this.navOptions();
19840
+ switch (event.key) {
19841
+ case 'ArrowDown':
19842
+ event.preventDefault();
19843
+ this.isOpen.set(true);
19844
+ if (options.length > 0)
19845
+ this.activeIndex.set((this.activeIndex() + 1) % options.length);
19846
+ break;
19847
+ case 'ArrowUp':
19848
+ event.preventDefault();
19849
+ if (options.length > 0) {
19850
+ this.activeIndex.set((this.activeIndex() - 1 + options.length) % options.length);
19851
+ }
19852
+ break;
19853
+ case 'Home':
19854
+ event.preventDefault();
19855
+ this.activeIndex.set(0);
19856
+ break;
19857
+ case 'End':
19858
+ event.preventDefault();
19859
+ this.activeIndex.set(Math.max(0, options.length - 1));
19860
+ break;
19861
+ case 'Enter': {
19862
+ event.preventDefault();
19863
+ const active = options[this.activeIndex()];
19864
+ if (active)
19865
+ this.pick(active);
19866
+ break;
19867
+ }
19868
+ case 'Escape':
19869
+ event.preventDefault();
19870
+ this.close();
19871
+ break;
19872
+ default:
19873
+ break;
19874
+ }
19875
+ }
19876
+ onOptionHover(index) {
19877
+ if (index !== this.activeIndex())
19878
+ this.activeIndex.set(index);
19879
+ }
19880
+ // ── Click-outside ────────────────────────────────────────────────────────────
19881
+ onDocumentMouseDown(ev) {
19882
+ if (!this.isOpen())
19883
+ return;
19884
+ const target = ev.target;
19885
+ if (target && this.host.nativeElement.contains(target))
19886
+ return;
19887
+ this.close();
19888
+ }
19889
+ // ── Search ───────────────────────────────────────────────────────────────────
19890
+ retry() {
19891
+ this._runSearch(this.searchTerm());
19892
+ }
19893
+ _scheduleSearch(q) {
19894
+ this._clearTimer();
19895
+ this._timer = setTimeout(() => this._runSearch(q), this.debounceMs());
19896
+ }
19897
+ _clearTimer() {
19898
+ if (this._timer !== null) {
19899
+ clearTimeout(this._timer);
19900
+ this._timer = null;
19901
+ }
19902
+ }
19903
+ _runSearch(q) {
19904
+ this._clearTimer();
19905
+ this._searchSub?.unsubscribe();
19906
+ this._searchStarted = true;
19907
+ this.loading.set(true);
19908
+ this.loadFailed.set(false);
19909
+ this._searchSub = this._search(q)
19910
+ .pipe(catchError(() => of(null)))
19911
+ .subscribe((rows) => {
19912
+ this.loading.set(false);
19913
+ if (rows === null) {
19914
+ this.loadFailed.set(true);
19915
+ this._results.set([]);
19916
+ return;
19917
+ }
19918
+ this._results.set(rows);
19919
+ this.activeIndex.set(0);
19920
+ });
19921
+ }
19922
+ _search(query) {
19923
+ const fetch = this.searchFn();
19924
+ if (fetch)
19925
+ return fetch(query);
19926
+ // No loader and no transport: degrade to "no options" — a picker must never throw
19927
+ // mid-keystroke. (Only reachable in a host without provideHttpClient and without [searchFn].)
19928
+ if (!this.http)
19929
+ return of([]);
19930
+ return this.http
19931
+ .get(FLY_STRATEGY_OBJECTIVES_LOOKUP_ENDPOINT, { params: { q: query, limit: 10 } })
19932
+ .pipe(map(flyUnwrapObjectiveLookup));
19933
+ }
19934
+ // ── Selection ────────────────────────────────────────────────────────────────
19935
+ pick(ref) {
19936
+ this._selected.set(ref);
19937
+ this.selectionChange.emit(ref);
19938
+ this.close();
19939
+ this.searchTerm.set('');
19940
+ this._results.set([]);
19941
+ this._searchStarted = false;
19942
+ }
19943
+ remove() {
19944
+ this._selected.set(null);
19945
+ this.selectionChange.emit(null);
19946
+ }
19947
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyStrategySelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
19948
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyStrategySelectorComponent, isStandalone: true, selector: "fly-strategy-selector", inputs: { searchFn: { classPropertyName: "searchFn", publicName: "searchFn", isSignal: true, isRequired: false, transformFunction: null }, initialSelection: { classPropertyName: "initialSelection", publicName: "initialSelection", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, debounceMs: { classPropertyName: "debounceMs", publicName: "debounceMs", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange" }, host: { listeners: { "document:mousedown": "onDocumentMouseDown($event)" }, properties: { "class.fly-strategy-selector--open": "isOpen()" }, classAttribute: "fly-strategy-selector" }, viewQueries: [{ propertyName: "searchEl", first: true, predicate: ["searchRef"], descendants: true }], ngImport: i0, template: "@if (selected(); as pick) {\r\n <div class=\"fly-strategy-selector__chip\" role=\"group\" [attr.aria-label]=\"pick.title\">\r\n <span class=\"fly-strategy-selector__chip-icon\" aria-hidden=\"true\">\r\n @if (pick.strategyIcon) {\r\n <i class=\"pi {{ pick.strategyIcon }}\"></i>\r\n } @else {\r\n <i class=\"pi pi-compass\"></i>\r\n }\r\n </span>\r\n <span class=\"fly-strategy-selector__chip-body\">\r\n <span class=\"fly-strategy-selector__chip-title\" [title]=\"pick.title\">{{ pick.title }}</span>\r\n <span class=\"fly-strategy-selector__chip-strategy\" [title]=\"pick.strategyTitle\">{{ pick.strategyTitle }}</span>\r\n </span>\r\n <button\r\n type=\"button\"\r\n class=\"fly-strategy-selector__chip-remove\"\r\n [attr.aria-label]=\"'strategy_selector.remove' | translate: { name: pick.title }\"\r\n (click)=\"remove()\">\r\n \u2715\r\n </button>\r\n </div>\r\n} @else {\r\n <div\r\n class=\"fly-strategy-selector__wrap\"\r\n [flyClickOutsideEnabled]=\"isOpen()\"\r\n (flyClickOutside)=\"close()\"\r\n >\r\n <input\r\n #searchRef\r\n type=\"text\"\r\n class=\"fly-strategy-selector__search-input\"\r\n role=\"combobox\"\r\n aria-autocomplete=\"list\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n aria-controls=\"fly-strategy-selector-listbox\"\r\n [attr.aria-activedescendant]=\"activeDescendant() || null\"\r\n [attr.aria-label]=\"placeholder() || ('strategy_selector.search_placeholder' | translate)\"\r\n [placeholder]=\"placeholder() || ('strategy_selector.search_placeholder' | translate)\"\r\n [value]=\"searchTerm()\"\r\n (focus)=\"open()\"\r\n (input)=\"onSearchInput($any($event.target).value)\"\r\n (keydown)=\"onPanelKeydown($event)\"\r\n />\r\n\r\n @if (isOpen()) {\r\n <div class=\"fly-strategy-selector__panel\">\r\n <div\r\n class=\"fly-strategy-selector__listbox\"\r\n role=\"listbox\"\r\n id=\"fly-strategy-selector-listbox\"\r\n [attr.aria-activedescendant]=\"activeDescendant() || null\"\r\n >\r\n @if (loading()) {\r\n <div class=\"fly-strategy-selector__status\" role=\"status\">\r\n {{ 'strategy_selector.loading' | translate }}\r\n </div>\r\n } @else if (loadFailed()) {\r\n <div class=\"fly-strategy-selector__status fly-strategy-selector__status--error\" role=\"alert\">\r\n <span>{{ 'strategy_selector.error' | translate }}</span>\r\n <button type=\"button\" class=\"fly-strategy-selector__retry\" (click)=\"retry()\">\r\n {{ 'strategy_selector.retry' | translate }}\r\n </button>\r\n </div>\r\n } @else {\r\n @for (group of groups(); track group.strategyId) {\r\n <div class=\"fly-strategy-selector__group\" role=\"presentation\">\r\n <i class=\"pi {{ group.strategyIcon || 'pi-compass' }}\" aria-hidden=\"true\"></i>\r\n <span class=\"fly-strategy-selector__group-title\">{{ group.strategyTitle }}</span>\r\n </div>\r\n @for (item of group.items; track item.id) {\r\n <div\r\n class=\"fly-strategy-selector__option\"\r\n role=\"option\"\r\n tabindex=\"-1\"\r\n [id]=\"optionId(navIndexOf(item.id))\"\r\n [attr.aria-selected]=\"false\"\r\n [class.fly-strategy-selector__option--active]=\"navIndexOf(item.id) === activeIndex()\"\r\n (click)=\"pick(item)\"\r\n (keydown.enter)=\"pick(item)\"\r\n (mouseenter)=\"onOptionHover(navIndexOf(item.id))\"\r\n >\r\n <span class=\"fly-strategy-selector__option-title\">{{ item.title }}</span>\r\n </div>\r\n }\r\n }\r\n\r\n @if (groups().length === 0) {\r\n <div class=\"fly-strategy-selector__status\" role=\"presentation\">\r\n {{ 'strategy_selector.no_results' | translate }}\r\n </div>\r\n }\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n}\r\n", styles: [":host{--_surface: var(--surface-card, #fff);--_surface-panel: var(--glass-bg-elevated, var(--surface-card, #fff));--_panel-blur: var(--glass-blur, 40px);--_border: var(--separator, var(--surface-border, #e2e8f0));--_text: var(--label-primary, var(--text-color, #0f172a));--_text-subtle: var(--label-secondary, var(--text-color-secondary, #64748b));--_fill: var(--fill-tertiary, #f3f4f6);--_surface-hover: var(--surface-hover, #f1f5f9);--_accent: var(--accent);--_primary: var(--primary-color, var(--_accent));--_danger: var(--system-red, #ef4444);--_radius: 8px;display:block;inline-size:100%;color:var(--_text);font-size:13px}:host,:host *,:host *:before,:host *:after{box-sizing:border-box}.fly-strategy-selector__chip{display:flex;align-items:center;gap:8px;padding-block:5px;padding-inline:8px;border:1px solid var(--_border);border-radius:10px;background:var(--_fill);max-inline-size:100%}.fly-strategy-selector__chip-icon{display:inline-flex;flex:none;align-items:center;justify-content:center;inline-size:22px;block-size:22px;border-radius:50%;background:color-mix(in srgb,var(--_accent, var(--_text-subtle)) 14%,transparent);color:var(--_accent, var(--_text-subtle));font-size:12px;line-height:1}.fly-strategy-selector__chip-body{display:flex;flex-direction:column;min-inline-size:0;flex:1}.fly-strategy-selector__chip-title{font-size:12px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-strategy-selector__chip-strategy{font-size:11px;color:var(--_text-subtle);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-strategy-selector__chip-remove{border:none;background:transparent;padding:4px;border-radius:50%;color:var(--_text-subtle);font-size:10px;line-height:1;cursor:pointer;flex:none}.fly-strategy-selector__chip-remove:hover{background:color-mix(in srgb,var(--_danger) 18%,transparent);color:var(--_danger)}.fly-strategy-selector__wrap{position:relative;inline-size:100%}.fly-strategy-selector__search-input{inline-size:100%;min-block-size:2.25rem;padding-block:5px;padding-inline:10px;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);color:var(--_text);font:inherit}.fly-strategy-selector__search-input:focus-visible{outline:2px solid var(--_primary);outline-offset:-1px}.fly-strategy-selector__panel{position:absolute;z-index:60;inset-inline:0;inset-block-start:calc(100% + 4px);display:flex;flex-direction:column;max-block-size:19rem;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface-panel);-webkit-backdrop-filter:blur(var(--_panel-blur));backdrop-filter:blur(var(--_panel-blur));box-shadow:0 12px 32px #0000002e;overflow:hidden}.fly-strategy-selector__listbox{flex:1 1 auto;overflow-y:auto;padding-block:4px}.fly-strategy-selector__group{display:flex;align-items:center;gap:6px;padding-block:6px 3px;padding-inline:10px;color:var(--_text-subtle);font-size:10px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.fly-strategy-selector__group .pi{font-size:11px}.fly-strategy-selector__group-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-strategy-selector__option{padding-block:6px;padding-inline:26px 10px;cursor:pointer}.fly-strategy-selector__option--active{background:var(--_surface-hover)}.fly-strategy-selector__option-title{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-strategy-selector__status{display:flex;align-items:center;gap:8px;padding-block:10px;padding-inline:10px;color:var(--_text-subtle)}.fly-strategy-selector__status--error{color:var(--_danger)}.fly-strategy-selector__retry{border:1px solid var(--_border);border-radius:6px;background:var(--_surface);padding-block:2px;padding-inline:8px;color:var(--_text);font:inherit;font-weight:600;cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: FlyClickOutsideDirective, selector: "[flyClickOutside]", inputs: ["flyClickOutsideEnabled", "flyClickOutsideIgnore"], outputs: ["flyClickOutside"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
19949
+ }
19950
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyStrategySelectorComponent, decorators: [{
19951
+ type: Component,
19952
+ args: [{ selector: 'fly-strategy-selector', standalone: true, imports: [CommonModule, TranslatePipe, FlyClickOutsideDirective], changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'fly-strategy-selector', '[class.fly-strategy-selector--open]': 'isOpen()' }, template: "@if (selected(); as pick) {\r\n <div class=\"fly-strategy-selector__chip\" role=\"group\" [attr.aria-label]=\"pick.title\">\r\n <span class=\"fly-strategy-selector__chip-icon\" aria-hidden=\"true\">\r\n @if (pick.strategyIcon) {\r\n <i class=\"pi {{ pick.strategyIcon }}\"></i>\r\n } @else {\r\n <i class=\"pi pi-compass\"></i>\r\n }\r\n </span>\r\n <span class=\"fly-strategy-selector__chip-body\">\r\n <span class=\"fly-strategy-selector__chip-title\" [title]=\"pick.title\">{{ pick.title }}</span>\r\n <span class=\"fly-strategy-selector__chip-strategy\" [title]=\"pick.strategyTitle\">{{ pick.strategyTitle }}</span>\r\n </span>\r\n <button\r\n type=\"button\"\r\n class=\"fly-strategy-selector__chip-remove\"\r\n [attr.aria-label]=\"'strategy_selector.remove' | translate: { name: pick.title }\"\r\n (click)=\"remove()\">\r\n \u2715\r\n </button>\r\n </div>\r\n} @else {\r\n <div\r\n class=\"fly-strategy-selector__wrap\"\r\n [flyClickOutsideEnabled]=\"isOpen()\"\r\n (flyClickOutside)=\"close()\"\r\n >\r\n <input\r\n #searchRef\r\n type=\"text\"\r\n class=\"fly-strategy-selector__search-input\"\r\n role=\"combobox\"\r\n aria-autocomplete=\"list\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n aria-controls=\"fly-strategy-selector-listbox\"\r\n [attr.aria-activedescendant]=\"activeDescendant() || null\"\r\n [attr.aria-label]=\"placeholder() || ('strategy_selector.search_placeholder' | translate)\"\r\n [placeholder]=\"placeholder() || ('strategy_selector.search_placeholder' | translate)\"\r\n [value]=\"searchTerm()\"\r\n (focus)=\"open()\"\r\n (input)=\"onSearchInput($any($event.target).value)\"\r\n (keydown)=\"onPanelKeydown($event)\"\r\n />\r\n\r\n @if (isOpen()) {\r\n <div class=\"fly-strategy-selector__panel\">\r\n <div\r\n class=\"fly-strategy-selector__listbox\"\r\n role=\"listbox\"\r\n id=\"fly-strategy-selector-listbox\"\r\n [attr.aria-activedescendant]=\"activeDescendant() || null\"\r\n >\r\n @if (loading()) {\r\n <div class=\"fly-strategy-selector__status\" role=\"status\">\r\n {{ 'strategy_selector.loading' | translate }}\r\n </div>\r\n } @else if (loadFailed()) {\r\n <div class=\"fly-strategy-selector__status fly-strategy-selector__status--error\" role=\"alert\">\r\n <span>{{ 'strategy_selector.error' | translate }}</span>\r\n <button type=\"button\" class=\"fly-strategy-selector__retry\" (click)=\"retry()\">\r\n {{ 'strategy_selector.retry' | translate }}\r\n </button>\r\n </div>\r\n } @else {\r\n @for (group of groups(); track group.strategyId) {\r\n <div class=\"fly-strategy-selector__group\" role=\"presentation\">\r\n <i class=\"pi {{ group.strategyIcon || 'pi-compass' }}\" aria-hidden=\"true\"></i>\r\n <span class=\"fly-strategy-selector__group-title\">{{ group.strategyTitle }}</span>\r\n </div>\r\n @for (item of group.items; track item.id) {\r\n <div\r\n class=\"fly-strategy-selector__option\"\r\n role=\"option\"\r\n tabindex=\"-1\"\r\n [id]=\"optionId(navIndexOf(item.id))\"\r\n [attr.aria-selected]=\"false\"\r\n [class.fly-strategy-selector__option--active]=\"navIndexOf(item.id) === activeIndex()\"\r\n (click)=\"pick(item)\"\r\n (keydown.enter)=\"pick(item)\"\r\n (mouseenter)=\"onOptionHover(navIndexOf(item.id))\"\r\n >\r\n <span class=\"fly-strategy-selector__option-title\">{{ item.title }}</span>\r\n </div>\r\n }\r\n }\r\n\r\n @if (groups().length === 0) {\r\n <div class=\"fly-strategy-selector__status\" role=\"presentation\">\r\n {{ 'strategy_selector.no_results' | translate }}\r\n </div>\r\n }\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n}\r\n", styles: [":host{--_surface: var(--surface-card, #fff);--_surface-panel: var(--glass-bg-elevated, var(--surface-card, #fff));--_panel-blur: var(--glass-blur, 40px);--_border: var(--separator, var(--surface-border, #e2e8f0));--_text: var(--label-primary, var(--text-color, #0f172a));--_text-subtle: var(--label-secondary, var(--text-color-secondary, #64748b));--_fill: var(--fill-tertiary, #f3f4f6);--_surface-hover: var(--surface-hover, #f1f5f9);--_accent: var(--accent);--_primary: var(--primary-color, var(--_accent));--_danger: var(--system-red, #ef4444);--_radius: 8px;display:block;inline-size:100%;color:var(--_text);font-size:13px}:host,:host *,:host *:before,:host *:after{box-sizing:border-box}.fly-strategy-selector__chip{display:flex;align-items:center;gap:8px;padding-block:5px;padding-inline:8px;border:1px solid var(--_border);border-radius:10px;background:var(--_fill);max-inline-size:100%}.fly-strategy-selector__chip-icon{display:inline-flex;flex:none;align-items:center;justify-content:center;inline-size:22px;block-size:22px;border-radius:50%;background:color-mix(in srgb,var(--_accent, var(--_text-subtle)) 14%,transparent);color:var(--_accent, var(--_text-subtle));font-size:12px;line-height:1}.fly-strategy-selector__chip-body{display:flex;flex-direction:column;min-inline-size:0;flex:1}.fly-strategy-selector__chip-title{font-size:12px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-strategy-selector__chip-strategy{font-size:11px;color:var(--_text-subtle);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-strategy-selector__chip-remove{border:none;background:transparent;padding:4px;border-radius:50%;color:var(--_text-subtle);font-size:10px;line-height:1;cursor:pointer;flex:none}.fly-strategy-selector__chip-remove:hover{background:color-mix(in srgb,var(--_danger) 18%,transparent);color:var(--_danger)}.fly-strategy-selector__wrap{position:relative;inline-size:100%}.fly-strategy-selector__search-input{inline-size:100%;min-block-size:2.25rem;padding-block:5px;padding-inline:10px;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);color:var(--_text);font:inherit}.fly-strategy-selector__search-input:focus-visible{outline:2px solid var(--_primary);outline-offset:-1px}.fly-strategy-selector__panel{position:absolute;z-index:60;inset-inline:0;inset-block-start:calc(100% + 4px);display:flex;flex-direction:column;max-block-size:19rem;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface-panel);-webkit-backdrop-filter:blur(var(--_panel-blur));backdrop-filter:blur(var(--_panel-blur));box-shadow:0 12px 32px #0000002e;overflow:hidden}.fly-strategy-selector__listbox{flex:1 1 auto;overflow-y:auto;padding-block:4px}.fly-strategy-selector__group{display:flex;align-items:center;gap:6px;padding-block:6px 3px;padding-inline:10px;color:var(--_text-subtle);font-size:10px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.fly-strategy-selector__group .pi{font-size:11px}.fly-strategy-selector__group-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-strategy-selector__option{padding-block:6px;padding-inline:26px 10px;cursor:pointer}.fly-strategy-selector__option--active{background:var(--_surface-hover)}.fly-strategy-selector__option-title{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-strategy-selector__status{display:flex;align-items:center;gap:8px;padding-block:10px;padding-inline:10px;color:var(--_text-subtle)}.fly-strategy-selector__status--error{color:var(--_danger)}.fly-strategy-selector__retry{border:1px solid var(--_border);border-radius:6px;background:var(--_surface);padding-block:2px;padding-inline:8px;color:var(--_text);font:inherit;font-weight:600;cursor:pointer}\n"] }]
19953
+ }], ctorParameters: () => [], propDecorators: { searchFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchFn", required: false }] }], initialSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialSelection", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], debounceMs: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounceMs", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], searchEl: [{
19954
+ type: ViewChild,
19955
+ args: ['searchRef']
19956
+ }], onDocumentMouseDown: [{
19957
+ type: HostListener,
19958
+ args: ['document:mousedown', ['$event']]
19959
+ }] } });
19960
+
19618
19961
  /** The platform endpoint the catalogue reads. Relative — the host's gateway + auth interceptors apply. */
19619
19962
  const FLY_CURRENCIES_BRIEF_ENDPOINT = '/api/currencies/brief';
19620
19963
  /**
@@ -19906,7 +20249,11 @@ class FlyCurrencySelectorComponent {
19906
20249
  * - not `locked()` — a frozen value is authoritative; proposing one contradicts the lock.
19907
20250
  * - the control is still empty — a written value always wins.
19908
20251
  *
19909
- * It fires at most once per instance: clearing the field by hand latches it off, so the value
20252
+ * It stays armed until something rules it out: a user commit (pick, clear, remove) or a
20253
+ * written non-empty value. A written NULL does not rule it out — the forms layer initializes
20254
+ * a fresh control by writing its empty value on a microtask, which can land AFTER a
20255
+ * synchronously-resolved default has already committed, and reading that write as a user
20256
+ * clear would erase the default. Clearing the field by hand does latch it off, so the value
19910
20257
  * cannot reappear and read as the clear having not registered.
19911
20258
  *
19912
20259
  * A host that supplied `[currencies]` or `[fetchFn]` is never taken to the platform endpoint —
@@ -19977,8 +20324,17 @@ class FlyCurrencySelectorComponent {
19977
20324
  _fetchStarted = false;
19978
20325
  /** Guards the once-per-instance tenant-default resolve. */
19979
20326
  _tenantDefaultStarted = false;
19980
- /** Latches once the default has been applied, or ruled out by any commit (pick, clear, write). */
19981
- _tenantDefaultSettled = false;
20327
+ /**
20328
+ * Latches once the default has been RULED OUT: a user commit (pick, clear, remove) or a
20329
+ * written non-empty value. Applying the default does NOT latch it, and neither does a null
20330
+ * write: `NgModel` syncs the model's initial value on a microtask (`resolvedPromise.then` in
20331
+ * its `_updateValue`), so a SYNCHRONOUS `tenantDefaultFetchFn` — `of(currency)`, an offline
20332
+ * lookup — commits before that first write lands. When the self-application latched this
20333
+ * guard, the stale null write erased the committed default AND pinned the guard off, so the
20334
+ * default never applied for exactly those hosts (async HTTP fetches always lost the race
20335
+ * and were unaffected).
20336
+ */
20337
+ _tenantDefaultRuledOut = false;
19982
20338
  /** The resolved row, kept so a `writeValue(null)` arriving AFTER the fetch can still use it. */
19983
20339
  _tenantDefaultRow = null;
19984
20340
  searchEl;
@@ -20275,10 +20631,13 @@ class FlyCurrencySelectorComponent {
20275
20631
  * Applies the resolved default to a still-empty control.
20276
20632
  *
20277
20633
  * Called from both ends of a race with no fixed winner: the fetch may land before the forms
20278
- * layer writes, or after it writes the empty value a form is built with.
20634
+ * layer writes, or after it writes the empty value a form is built with. It can also run
20635
+ * AGAIN after applying — the forms layer's deferred initial null write clears the codes and
20636
+ * re-enters here — in which case it re-commits the same row, which also re-syncs the
20637
+ * `FormControl` the null write just reset.
20279
20638
  */
20280
20639
  _applyTenantDefaultIfEmpty() {
20281
- if (this._tenantDefaultSettled)
20640
+ if (this._tenantDefaultRuledOut)
20282
20641
  return;
20283
20642
  if (!this.applyTenantDefault() || this.isMulti() || this.locked())
20284
20643
  return;
@@ -20293,14 +20652,16 @@ class FlyCurrencySelectorComponent {
20293
20652
  // on success, and leaves this seed alone on failure.
20294
20653
  if (this.currencies() === null && this._loaded().length === 0)
20295
20654
  this._loaded.set([row]);
20296
- this._commit([row.code]);
20655
+ this._commit([row.code], { fromTenantDefault: true });
20297
20656
  }
20298
20657
  // ── ControlValueAccessor ───────────────────────────────────────────────────
20299
20658
  writeValue(value) {
20300
20659
  if (value === null || value === undefined) {
20301
20660
  this._selectedCodes.set([]);
20302
20661
  // An empty write re-tries the default - this is the common `patchValue`-after-load path,
20303
- // and on a fresh form it is the write that arrives BEFORE the fetch has landed.
20662
+ // and on a fresh form it is the write that arrives BEFORE the fetch has landed. It is
20663
+ // ALSO NgModel's microtask-deferred initial write arriving AFTER a synchronous fetch has
20664
+ // committed — re-trying restores the default that write just cleared.
20304
20665
  this._applyTenantDefaultIfEmpty();
20305
20666
  return;
20306
20667
  }
@@ -20308,7 +20669,7 @@ class FlyCurrencySelectorComponent {
20308
20669
  this._selectedCodes.set(codes);
20309
20670
  // A written value is the host's own answer; there is no hole left for a default to fill.
20310
20671
  if (codes.length > 0)
20311
- this._tenantDefaultSettled = true;
20672
+ this._tenantDefaultRuledOut = true;
20312
20673
  else
20313
20674
  this._applyTenantDefaultIfEmpty();
20314
20675
  }
@@ -20326,11 +20687,14 @@ class FlyCurrencySelectorComponent {
20326
20687
  const codes = this._selectedCodes();
20327
20688
  return this.isMulti() ? [...codes] : (codes[0] ?? null);
20328
20689
  }
20329
- _commit(codes) {
20690
+ _commit(codes, opts) {
20330
20691
  // Every user-driven change routes through here - pick, toggle, remove, clear - so this is the
20331
20692
  // one place that has to latch the default off. A cleared field that refilled itself would be
20332
- // indistinguishable from the clear never having registered.
20333
- this._tenantDefaultSettled = true;
20693
+ // indistinguishable from the clear never having registered. The one caller that must NOT
20694
+ // latch is the default's own application: latching there let the forms layer's deferred
20695
+ // initial writeValue(null) erase a synchronously-fetched default for good.
20696
+ if (!opts?.fromTenantDefault)
20697
+ this._tenantDefaultRuledOut = true;
20334
20698
  this._selectedCodes.set([...codes]);
20335
20699
  this._onChange(this._controlValue());
20336
20700
  this.selectionChange.emit([...codes]);
@@ -26524,5 +26888,5 @@ const AUDIENCE_ERROR_CODES = {
26524
26888
  * Generated bundle index. Do not edit.
26525
26889
  */
26526
26890
 
26527
- export { AGENT_DRAG_MIME, AGENT_PAYLOAD_VERSION, APP_LOOKUP, AUDIENCE_ERROR_CODES, AUDIENCE_LIMITS, AUDIENCE_PRESETS, AUDIENCE_TERM_KINDS, AgentActionBus, AgentActionUnsupportedDispatchError, AgentCommandRegistry, AgentDropRegistry, AgentFlightAnimator, AgentLookupRegistry, AgentPayloadOversizeError, AudienceBuilderComponent, AuthService, BilingualFieldComponent, CRON_MODES, CRON_WEEKDAYS, ContextMenuComponent, DEFAULT_AGENT_PAYLOAD_LIMITS, DEFAULT_FLY_THEME_MODE, DS_BASELINE_LOCALES, DashboardKpiComponent, DialogResult, ENTITY_LINK_LAUNCHER, EntityLookupComponent, FLYOS_LAUNCH_EVENT, FLYOS_LAUNCH_REQUEST_EVENT, FLYOS_REMOTE_ROUTE_EVENT, FLY_ACCENT_PROPERTY, FLY_ADMIN_ROLES, FLY_CHUNK_RELOAD_FLAG, FLY_COMPACT_THRESHOLD, FLY_COUNTRIES, FLY_CURRENCIES_BRIEF_ENDPOINT, FLY_CURRENCY_DEFAULT_ENDPOINT, FLY_EMOJI_BY_ID, FLY_EMOJI_CATEGORIES, FLY_EMOJI_CATEGORY_BY_ID, FLY_EMOJI_DEFAULT_CATEGORIES, FLY_EMOJI_PACK, FLY_EMOJI_PACK_MISSING_MESSAGE, FLY_EMOJI_PREVIEW_COUNT, FLY_EMPTY_VALUE, FLY_LOCALE_CATALOG, FLY_MAGIC_BAR_EVENT, FLY_MAGIC_BAR_ICONS, FLY_MAGIC_BAR_STORE_KEY, FLY_NF_CACHE_HEAL_FLAG, FLY_RELOAD_DEBOUNCE_MS, FLY_RELOAD_REARM_MS, FLY_REMOTE_BASE_PATH, FLY_REMOTE_CONTEXT_EVENT, FLY_REMOTE_CONTEXT_STORE_KEY, FLY_REMOTE_ROUTES, FLY_SCAN_PENDING_DEFAULT_DELAY_MS, FLY_SCAN_PENDING_MAX_RETRIES, FLY_SCAN_PENDING_STATUS, FLY_SEARCH_DEBOUNCE_MS, FLY_SKIN_TONES, FLY_STANDALONE_AUTH_CONFIG, FLY_THEME_MODE_IDS, FLY_VIEWPORT_IS_MOBILE, FLY_WINDOW_BREADCRUMBS_EVENT, FLY_WINDOW_BREADCRUMBS_STORE_KEY, FLY_WINDOW_HELP_HINT_EVENT, FLY_WINDOW_PAGE_TITLE_EVENT, FlyAchievementProgressComponent, FlyActionMenuComponent, FlyActionStackComponent, FlyAgentDraggableDirective, FlyAnimatedEmojiComponent, FlyAppHomeComponent, FlyAppTopbarComponent, FlyAppUnavailableComponent, FlyAuthLiveRefresh, FlyBadgeWallComponent, FlyBlockUiComponent, FlyBreadcrumbComponent, FlyButtonComponent, FlyBytesPipe, FlyCaptchaComponent, FlyCardActionsComponent, FlyCardBodyComponent, FlyCardComponent, FlyCardFooterComponent, FlyCardGridComponent, FlyCardMediaComponent, FlyCardMetaComponent, FlyCardStatusComponent, FlyCardTitleComponent, FlyCellDirective, FlyCheckboxComponent, FlyChipComponent, FlyChunkReloadErrorHandler, FlyClickOutsideDirective, FlyCollapsibleComponent, FlyCommentThreadComponent, FlyCompactNumberPipe, FlyConfirmDialogComponent, FlyControlDirective, FlyCronBuilderComponent, FlyCurrencyCatalogService, FlyCurrencySelectorComponent, FlyDataTableComponent, FlyDatePipe, FlyDateTimePipe, FlyDebouncer, FlyDecimalNumberPipe, FlyDeepLinkPrefetchService, FlyDetailCardComponent, FlyDetailShellComponent, FlyDrawerComponent, FlyDurationPipe, FlyDynamicFormComponent, FlyEmojiPickerComponent, FlyFieldComponent, FlyFileDownloadService, FlyFileUploadComponent, FlyFilterPanelComponent, FlyFormBannerComponent, FlyFormFooterComponent, FlyFormGridComponent, FlyFormSectionComponent, FlyFullNumberPipe, FlyGanttComponent, FlyHubClient, FlyIconButtonComponent, FlyIfAdminDirective, FlyIfRoleDirective, FlyImageUploadComponent, FlyLeaderboardComponent, FlyMagicActionsComponent, FlyMetaListComponent, FlyModalComponent, FlyModerationQueueComponent, FlyModuleIconDirective, FlyMoneyPipe, FlyPagerComponent, FlyPaginationComponent, FlyPeoplePickerComponent, FlyPointsTierComponent, FlyProgressComponent, FlyRelativeTimePipe, FlyRemoteContextService, FlyRemoteRouter, FlyRemoteRouterOutletComponent, FlySearchInputComponent, FlySectionHeaderComponent, FlySecureSrcDirective, FlySegmentedComponent, FlySelectComponent, FlySkeletonComponent, FlySliderComponent, FlySparklineComponent, FlySpinnerComponent, FlyStandaloneAuthCallbackComponent, FlyStandaloneAuthService, FlyStandaloneMagicActionsComponent, FlyStateMessageComponent, FlyDynamicFormComponent as FlySurveyFormComponent, FlyTabComponent, FlyTabsComponent, FlyTagsInputComponent, FlyThemeService, FlyTimePipe, FlyToastService, FlyToggleComponent, FlyTooltipDirective, FlyTreeNavComponent, FlyTypeaheadComponent, FlyUserDirectoryService, FlyWindowHelpService, FlyWindowTitleService, FlyosPendingLaunchesGlobalKey, FlyosShellHandlesLaunchRequestsGlobalKey, FlyosShellOwnsHistoryGlobalKey, GANTT_DEPENDENCY_TYPES, GANTT_ROW_TINT_ALPHA, GANTT_ZOOMS, I18nService, LAUNCH_CONTEXT, MAGIC_BAR_SEARCH_WIDTH_DEFAULT, MAGIC_BAR_SEARCH_WIDTH_MAX, MAGIC_BAR_SEARCH_WIDTH_MIN, MagicBarRegistry, MessageBoxButtons, MessageBoxComponent, MessageBoxIcon, MessageBoxService, MockAuthService, NOVA_PRIORITY_TONE, NOVA_STATUS_TONE, NOVA_TONE_FALLBACK, OverlayStack, PRESENCE_COLORS, PageHeaderComponent, RTL_LOCALE_SET, SHARE_ORG_CHART_SYSTEM_KEY_APPS, SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT, SHARE_PANEL_DEFAULT_FILE_LEVELS, STATE_DEFAULT_ICONS, STATE_DEFAULT_MESSAGE_KEYS, STEP_UP_CODE, STEP_UP_REAUTH_HANDLER, STEP_UP_RETURN_KEY, SUPPORTED_AGENT_PAYLOAD_VERSIONS, SharePanelComponent, SourceAppResolver, StandaloneWindowManagerService, StatusBadgeComponent, StepUpService, ToastHostComponent, TranslatePipe, WINDOW_DATA, WINDOW_HELP_HINT, WindowBreadcrumbsRegistry, WindowManagerService, applySkinTone, applySuggestion, ariaSort, buildCron, canConfirm, canGoNext$1 as canGoNext, canGoPrev$1 as canGoPrev, captureFocus, clampMagicBarSearchWidth, clampPage$1 as clampPage, clampSliderValue, connectRemoteLaunch, enterActivatesNatively, filterGroup, filterSuggestions, findLocaleByDialect, findLocaleByPrefix, firstEnabledIndex, firstModuleIndex, flattenModules, flyAdminGuard, flyApiErrorMessage, flyCaptchaBase64Utf8, flyCaptchaBuildToken, flyCaptchaSha256Hex, flyCompactNumber, flyDebounced, flyDecimalNumber, flyDownloadBlob, flyDuration, flyEmojiFor, flyEmojiIsTonable, flyEmojiPackUrl, flyExportFileName, flyFirstEnabledIndex, flyFormatBytes, flyFormatDate, flyFormatDateTime, flyFormatMoney, flyFormatTime, flyFullNumber, flyNextEnabledIndex, flyNextSegmentIndex, flyRelativeTime, flyResolveActiveTab, flyRoleGuard, flyScanPendingDelayMs, flyScanRetry, flySignedCompact, flySkinToneLabelKey, flyStandaloneAuthGuard, flyStandaloneAuthInterceptor, flyToDateOnly, flyToPage, flyUnwrap, flyUnwrapCurrencies, flyUnwrapCurrency, flyUnwrapLenient, hasAnyRoleIn, healNativeFederationCacheOnce, initialExpanded, isAccentColor, isChunkLoadError, isFlyAuthStorePopulated, isNativeFederationCacheError, isRtlLocale, isRtlLocaleEntry, isValidCron, isValidSingleField, loadRemoteStyles, magicBarOwnerKey, magicBarSearchText, matchFlyRoutePattern, nextEnabledIndex, nextModuleIndex, nextSegmentIndex, nextSort, normalizeFlyTheme, normalizeRoles, novaPriorityToneVar, novaStatusToneVar, overlayStack, canGoNext as pagerCanGoNext, canGoPrev as pagerCanGoPrev, clampPage as pagerClampPage, parseCron, parseStepUpChallenge, parseTags, prefetchRemoteStyles, presenceColorFor, printConsoleSecurityWarning, provideFlyChunkReloadRecovery, provideFlyEmojiPack, provideFlyStandaloneAuth, reloadOnceForChunkError, requestAppLaunch, resolveActiveModule, resolveActiveSection, resolveActiveTab, resolveCellValue, resolveStateIcon, resolveStateMessageKey, restoreFocus, sameTags, sectionHintKey, sectionLayout, sliderTrackGradient, sparklinePath, sparklinePoints, stepUpInterceptor, stepUpReturnKey, trimAgentPayload, trimAgentString, unloadRemoteStyles, utf8ByteLength, validateAgentPayload, warnIfEmbeddedSessionMissing };
26891
+ export { AGENT_DRAG_MIME, AGENT_PAYLOAD_VERSION, APP_LOOKUP, AUDIENCE_ERROR_CODES, AUDIENCE_LIMITS, AUDIENCE_PRESETS, AUDIENCE_TERM_KINDS, AgentActionBus, AgentActionUnsupportedDispatchError, AgentCommandRegistry, AgentDropRegistry, AgentFlightAnimator, AgentLookupRegistry, AgentPayloadOversizeError, AudienceBuilderComponent, AuthService, BilingualFieldComponent, CRON_MODES, CRON_WEEKDAYS, ContextMenuComponent, DEFAULT_AGENT_PAYLOAD_LIMITS, DEFAULT_FLY_THEME_MODE, DS_BASELINE_LOCALES, DashboardKpiComponent, DialogResult, ENTITY_LINK_LAUNCHER, EntityLookupComponent, FLYOS_LAUNCH_EVENT, FLYOS_LAUNCH_REQUEST_EVENT, FLYOS_REMOTE_ROUTE_EVENT, FLY_ACCENT_PROPERTY, FLY_ADMIN_ROLES, FLY_CHUNK_RELOAD_FLAG, FLY_COMPACT_THRESHOLD, FLY_COUNTRIES, FLY_CURRENCIES_BRIEF_ENDPOINT, FLY_CURRENCY_DEFAULT_ENDPOINT, FLY_EMOJI_BY_ID, FLY_EMOJI_CATEGORIES, FLY_EMOJI_CATEGORY_BY_ID, FLY_EMOJI_DEFAULT_CATEGORIES, FLY_EMOJI_PACK, FLY_EMOJI_PACK_MISSING_MESSAGE, FLY_EMOJI_PREVIEW_COUNT, FLY_EMPTY_VALUE, FLY_LOCALE_CATALOG, FLY_MAGIC_BAR_EVENT, FLY_MAGIC_BAR_ICONS, FLY_MAGIC_BAR_STORE_KEY, FLY_NF_CACHE_HEAL_FLAG, FLY_RELOAD_DEBOUNCE_MS, FLY_RELOAD_REARM_MS, FLY_REMOTE_BASE_PATH, FLY_REMOTE_CONTEXT_EVENT, FLY_REMOTE_CONTEXT_STORE_KEY, FLY_REMOTE_ROUTES, FLY_SCAN_PENDING_DEFAULT_DELAY_MS, FLY_SCAN_PENDING_MAX_RETRIES, FLY_SCAN_PENDING_STATUS, FLY_SEARCH_DEBOUNCE_MS, FLY_SKIN_TONES, FLY_STANDALONE_AUTH_CONFIG, FLY_STRATEGY_OBJECTIVES_LOOKUP_ENDPOINT, FLY_THEME_MODE_IDS, FLY_VIEWPORT_IS_MOBILE, FLY_WINDOW_BREADCRUMBS_EVENT, FLY_WINDOW_BREADCRUMBS_STORE_KEY, FLY_WINDOW_HELP_HINT_EVENT, FLY_WINDOW_PAGE_TITLE_EVENT, FlyAchievementProgressComponent, FlyActionMenuComponent, FlyActionStackComponent, FlyAgentDraggableDirective, FlyAnimatedEmojiComponent, FlyAppHomeComponent, FlyAppTopbarComponent, FlyAppUnavailableComponent, FlyAuthLiveRefresh, FlyBadgeWallComponent, FlyBlockUiComponent, FlyBreadcrumbComponent, FlyButtonComponent, FlyBytesPipe, FlyCaptchaComponent, FlyCardActionsComponent, FlyCardBodyComponent, FlyCardComponent, FlyCardFooterComponent, FlyCardGridComponent, FlyCardMediaComponent, FlyCardMetaComponent, FlyCardStatusComponent, FlyCardTitleComponent, FlyCellDirective, FlyCheckboxComponent, FlyChipComponent, FlyChunkReloadErrorHandler, FlyClickOutsideDirective, FlyCollapsibleComponent, FlyCommentThreadComponent, FlyCompactNumberPipe, FlyConfirmDialogComponent, FlyControlDirective, FlyCronBuilderComponent, FlyCurrencyCatalogService, FlyCurrencySelectorComponent, FlyDataTableComponent, FlyDatePipe, FlyDateTimePipe, FlyDebouncer, FlyDecimalNumberPipe, FlyDeepLinkPrefetchService, FlyDetailCardComponent, FlyDetailShellComponent, FlyDrawerComponent, FlyDurationPipe, FlyDynamicFormComponent, FlyEmojiPickerComponent, FlyFieldComponent, FlyFileDownloadService, FlyFileUploadComponent, FlyFilterPanelComponent, FlyFormBannerComponent, FlyFormFooterComponent, FlyFormGridComponent, FlyFormSectionComponent, FlyFullNumberPipe, FlyGanttComponent, FlyHubClient, FlyIconButtonComponent, FlyIfAdminDirective, FlyIfRoleDirective, FlyImageUploadComponent, FlyLeaderboardComponent, FlyMagicActionsComponent, FlyMetaListComponent, FlyModalComponent, FlyModerationQueueComponent, FlyModuleIconDirective, FlyMoneyPipe, FlyPagerComponent, FlyPaginationComponent, FlyPeoplePickerComponent, FlyPointsTierComponent, FlyProgressComponent, FlyRelativeTimePipe, FlyRemoteContextService, FlyRemoteRouter, FlyRemoteRouterOutletComponent, FlySearchInputComponent, FlySectionHeaderComponent, FlySecureSrcDirective, FlySegmentedComponent, FlySelectComponent, FlySkeletonComponent, FlySliderComponent, FlySparklineComponent, FlySpinnerComponent, FlyStandaloneAuthCallbackComponent, FlyStandaloneAuthService, FlyStandaloneMagicActionsComponent, FlyStateMessageComponent, FlyStrategySelectorComponent, FlyDynamicFormComponent as FlySurveyFormComponent, FlyTabComponent, FlyTabsComponent, FlyTagsInputComponent, FlyThemeService, FlyTimePipe, FlyToastService, FlyToggleComponent, FlyTooltipDirective, FlyTreeNavComponent, FlyTypeaheadComponent, FlyUserDirectoryService, FlyWindowHelpService, FlyWindowTitleService, FlyosPendingLaunchesGlobalKey, FlyosShellHandlesLaunchRequestsGlobalKey, FlyosShellOwnsHistoryGlobalKey, GANTT_DEPENDENCY_TYPES, GANTT_ROW_TINT_ALPHA, GANTT_ZOOMS, I18nService, LAUNCH_CONTEXT, MAGIC_BAR_SEARCH_WIDTH_DEFAULT, MAGIC_BAR_SEARCH_WIDTH_MAX, MAGIC_BAR_SEARCH_WIDTH_MIN, MagicBarRegistry, MessageBoxButtons, MessageBoxComponent, MessageBoxIcon, MessageBoxService, MockAuthService, NOVA_PRIORITY_TONE, NOVA_STATUS_TONE, NOVA_TONE_FALLBACK, OverlayStack, PRESENCE_COLORS, PageHeaderComponent, RTL_LOCALE_SET, SHARE_ORG_CHART_SYSTEM_KEY_APPS, SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT, SHARE_PANEL_DEFAULT_FILE_LEVELS, STATE_DEFAULT_ICONS, STATE_DEFAULT_MESSAGE_KEYS, STEP_UP_CODE, STEP_UP_REAUTH_HANDLER, STEP_UP_RETURN_KEY, SUPPORTED_AGENT_PAYLOAD_VERSIONS, SharePanelComponent, SourceAppResolver, StandaloneWindowManagerService, StatusBadgeComponent, StepUpService, ToastHostComponent, TranslatePipe, WINDOW_DATA, WINDOW_HELP_HINT, WindowBreadcrumbsRegistry, WindowManagerService, applySkinTone, applySuggestion, ariaSort, buildCron, canConfirm, canGoNext$1 as canGoNext, canGoPrev$1 as canGoPrev, captureFocus, clampMagicBarSearchWidth, clampPage$1 as clampPage, clampSliderValue, connectRemoteLaunch, enterActivatesNatively, filterGroup, filterSuggestions, findLocaleByDialect, findLocaleByPrefix, firstEnabledIndex, firstModuleIndex, flattenModules, flyAdminGuard, flyApiErrorMessage, flyCaptchaBase64Utf8, flyCaptchaBuildToken, flyCaptchaSha256Hex, flyCompactNumber, flyDebounced, flyDecimalNumber, flyDownloadBlob, flyDuration, flyEmojiFor, flyEmojiIsTonable, flyEmojiPackUrl, flyExportFileName, flyFirstEnabledIndex, flyFormatBytes, flyFormatDate, flyFormatDateTime, flyFormatMoney, flyFormatTime, flyFullNumber, flyNextEnabledIndex, flyNextSegmentIndex, flyRelativeTime, flyResolveActiveTab, flyRoleGuard, flyScanPendingDelayMs, flyScanRetry, flySignedCompact, flySkinToneLabelKey, flyStandaloneAuthGuard, flyStandaloneAuthInterceptor, flyToDateOnly, flyToPage, flyUnwrap, flyUnwrapCurrencies, flyUnwrapCurrency, flyUnwrapLenient, flyUnwrapObjectiveLookup, hasAnyRoleIn, healNativeFederationCacheOnce, initialExpanded, isAccentColor, isChunkLoadError, isFlyAuthStorePopulated, isNativeFederationCacheError, isRtlLocale, isRtlLocaleEntry, isValidCron, isValidSingleField, loadRemoteStyles, magicBarOwnerKey, magicBarSearchText, matchFlyRoutePattern, nextEnabledIndex, nextModuleIndex, nextSegmentIndex, nextSort, normalizeFlyTheme, normalizeRoles, novaPriorityToneVar, novaStatusToneVar, overlayStack, canGoNext as pagerCanGoNext, canGoPrev as pagerCanGoPrev, clampPage as pagerClampPage, parseCron, parseStepUpChallenge, parseTags, prefetchRemoteStyles, presenceColorFor, printConsoleSecurityWarning, provideFlyChunkReloadRecovery, provideFlyEmojiPack, provideFlyStandaloneAuth, reloadOnceForChunkError, requestAppLaunch, resolveActiveModule, resolveActiveSection, resolveActiveTab, resolveCellValue, resolveStateIcon, resolveStateMessageKey, restoreFocus, sameTags, sectionHintKey, sectionLayout, sliderTrackGradient, sparklinePath, sparklinePoints, stepUpInterceptor, stepUpReturnKey, trimAgentPayload, trimAgentString, unloadRemoteStyles, utf8ByteLength, validateAgentPayload, warnIfEmbeddedSessionMissing };
26528
26892
  //# sourceMappingURL=flyos-design-system.mjs.map