@mohamedatia/fly-design-system 2.4.0 → 2.6.1

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.
@@ -1,8 +1,8 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, signal, computed, Injectable, inject, ErrorHandler, PLATFORM_ID, Pipe, DOCUMENT, ElementRef, input, output, HostListener, ViewChild, ChangeDetectionStrategy, Component, EventEmitter, DestroyRef, Output, Input, forwardRef, Injector, viewChild, effect, afterNextRender, ViewEncapsulation, model } from '@angular/core';
2
+ import { InjectionToken, signal, computed, Injectable, inject, ErrorHandler, PLATFORM_ID, Pipe, DOCUMENT, ElementRef, input, output, HostListener, ViewChild, ChangeDetectionStrategy, Component, EventEmitter, DestroyRef, Output, Input, forwardRef, Injector, viewChild, effect, afterNextRender, ViewEncapsulation, model, Directive } from '@angular/core';
3
3
  import * as i1$1 from '@angular/common';
4
- import { isPlatformBrowser, CommonModule } from '@angular/common';
5
- import { Router } from '@angular/router';
4
+ import { isPlatformBrowser, CommonModule, DOCUMENT as DOCUMENT$1 } from '@angular/common';
5
+ import { Router, NavigationEnd } from '@angular/router';
6
6
  import { of, ReplaySubject, Subject } from 'rxjs';
7
7
  import * as i1 from '@angular/forms';
8
8
  import { FormsModule, NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
@@ -375,6 +375,152 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
375
375
  type: Injectable
376
376
  }] });
377
377
 
378
+ /**
379
+ * FlyOS standard navigation surface for Business / Supporting App remotes.
380
+ *
381
+ * Problem this solves
382
+ * -------------------
383
+ * A federated remote ships with its own Angular `Routes` table that works
384
+ * perfectly in **standalone** mode (e.g. `http://localhost:7202/signals/:id`).
385
+ * Once the same remote is mounted inside the FlyOS desktop shell via
386
+ * `*ngComponentOutlet`, `inject(Router)` resolves to the **host shell's**
387
+ * Router — which has no knowledge of `/signals/:id`. Calls like
388
+ * `router.navigate(['/signals', id])` then silently no-op and the click
389
+ * "does nothing."
390
+ *
391
+ * The fix
392
+ * -------
393
+ * Inject `FlyRemoteRouter` instead of `Router`. It mirrors `Router.navigate` /
394
+ * `Router.navigateByUrl` and:
395
+ *
396
+ * - **Standalone** (no `WINDOW_DATA`): delegates to the real Angular Router,
397
+ * so the URL bar, browser back button, and `RouterLink` continue to work
398
+ * exactly as before.
399
+ * - **Embedded** (mounted in the shell via `WINDOW_DATA`): writes the target
400
+ * URL to an internal signal (`url`, `segments`) and does **not** touch the
401
+ * host's Router. The remote's root component watches the signal and renders
402
+ * the right view (typically a `@switch` on the first segment, plus an inline
403
+ * overlay for detail pages).
404
+ *
405
+ * Usage in a list page
406
+ * --------------------
407
+ * ```ts
408
+ * private readonly router = inject(FlyRemoteRouter);
409
+ *
410
+ * openDetail(item: Signal): void {
411
+ * this.router.navigate(['/signals', item.id]); // works in both modes
412
+ * }
413
+ * ```
414
+ *
415
+ * Usage in the remote's root component (embedded dispatcher)
416
+ * ----------------------------------------------------------
417
+ * ```ts
418
+ * private readonly router = inject(FlyRemoteRouter);
419
+ *
420
+ * readonly view = computed(() => {
421
+ * if (!this.router.isEmbedded) return null; // standalone: <router-outlet> handles it
422
+ * const [head, id] = this.router.segments();
423
+ * if (head === 'signals' && id) return { kind: 'signal-detail', id };
424
+ * if (head === 'signals') return { kind: 'signals-list' };
425
+ * return { kind: head ?? 'home' };
426
+ * });
427
+ * ```
428
+ *
429
+ * Notes
430
+ * -----
431
+ * - `WINDOW_DATA` is the canonical "I am running inside the shell" marker; the
432
+ * shell provides it per window, and it is `null` everywhere else.
433
+ * - `Router` is injected as `optional`. A remote that does not register a Router
434
+ * (e.g. a tiny chromeless utility app) still gets a working `navigate` that
435
+ * updates the `url` signal in embedded mode.
436
+ */
437
+ class FlyRemoteRouter {
438
+ windowData = inject(WINDOW_DATA, { optional: true });
439
+ router = inject(Router, { optional: true });
440
+ /** True when this remote is rendered inside the FlyOS desktop shell. */
441
+ isEmbedded = this.windowData != null;
442
+ _url = signal('/', ...(ngDevMode ? [{ debugName: "_url" }] : /* istanbul ignore next */ []));
443
+ /**
444
+ * Current logical URL of the remote — `/signals/abc` etc.
445
+ *
446
+ * Standalone: mirrors `Router.url` and is updated on every `NavigationEnd`.
447
+ * Embedded: updated by `navigate` / `navigateByUrl` only.
448
+ */
449
+ url = this._url.asReadonly();
450
+ /**
451
+ * `url` parsed into path segments, e.g. `'/signals/abc'` → `['signals', 'abc']`.
452
+ * Query string and hash are stripped. Empty segments removed.
453
+ */
454
+ segments = computed(() => {
455
+ const u = this._url();
456
+ return u.split('?')[0].split('#')[0].split('/').filter(Boolean);
457
+ }, ...(ngDevMode ? [{ debugName: "segments" }] : /* istanbul ignore next */ []));
458
+ constructor() {
459
+ if (!this.isEmbedded && this.router) {
460
+ // Seed with the current standalone URL so consumers can read `segments()`
461
+ // synchronously during initial render, before any NavigationEnd fires.
462
+ this._url.set(this.router.url);
463
+ this.router.events.subscribe(event => {
464
+ if (event instanceof NavigationEnd) {
465
+ this._url.set(event.urlAfterRedirects);
466
+ }
467
+ });
468
+ }
469
+ }
470
+ /**
471
+ * Navigate to a route, identified by an array of commands as you would pass
472
+ * to Angular's `Router.navigate`. In embedded mode `extras` is ignored — the
473
+ * shell owns history and query-param handling for the window's URL.
474
+ */
475
+ navigate(commands, extras) {
476
+ if (!this.isEmbedded && this.router) {
477
+ return this.router.navigate(commands, extras);
478
+ }
479
+ this._url.set(this.buildUrl(commands));
480
+ return Promise.resolve(true);
481
+ }
482
+ /**
483
+ * Navigate to a route given as a full URL string. Mirrors `Router.navigateByUrl`.
484
+ */
485
+ navigateByUrl(url, extras) {
486
+ if (!this.isEmbedded && this.router) {
487
+ return this.router.navigateByUrl(url, extras);
488
+ }
489
+ this._url.set(url.startsWith('/') ? url : '/' + url);
490
+ return Promise.resolve(true);
491
+ }
492
+ /**
493
+ * Convenience: pop the last two segments (the typical "go back from detail
494
+ * to list" gesture). In standalone mode this falls through to `history.back()`
495
+ * so the browser's back stack is respected.
496
+ */
497
+ back() {
498
+ if (!this.isEmbedded) {
499
+ if (typeof history !== 'undefined')
500
+ history.back();
501
+ return;
502
+ }
503
+ const segs = [...this.segments()];
504
+ if (segs.length === 0)
505
+ return;
506
+ segs.pop();
507
+ this._url.set('/' + segs.join('/'));
508
+ }
509
+ buildUrl(commands) {
510
+ const parts = commands
511
+ .map(c => (c == null ? '' : String(c)))
512
+ .map(s => s.replace(/^\/+|\/+$/g, ''))
513
+ .filter(Boolean);
514
+ return '/' + parts.join('/');
515
+ }
516
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyRemoteRouter, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
517
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyRemoteRouter, providedIn: 'root' });
518
+ }
519
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyRemoteRouter, decorators: [{
520
+ type: Injectable,
521
+ args: [{ providedIn: 'root' }]
522
+ }], ctorParameters: () => [] });
523
+
378
524
  /**
379
525
  * Translates a key using the shared I18nService.
380
526
  *
@@ -619,11 +765,11 @@ class ContextMenuComponent {
619
765
  this.previouslyFocused = null;
620
766
  }
621
767
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ContextMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
622
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: ContextMenuComponent, isStandalone: true, selector: "fly-context-menu", inputs: { x: { classPropertyName: "x", publicName: "x", isSignal: true, isRequired: true, transformFunction: null }, y: { classPropertyName: "y", publicName: "y", isSignal: true, isRequired: true, transformFunction: null }, sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { action: "action", closed: "closed" }, host: { listeners: { "document:mousedown": "onClickOutside($event)", "document:keydown.escape": "onEscape()", "document:contextmenu": "onContextMenu($event)", "keydown": "onKeydown($event)" } }, viewQueries: [{ propertyName: "menuEl", first: true, predicate: ["contextMenu"], descendants: true }], ngImport: i0, template: "<div\n #contextMenu\n class=\"context-menu\"\n [style.left.px]=\"clampedPos().left\"\n [style.top.px]=\"clampedPos().top\"\n role=\"menu\"\n [attr.aria-label]=\"'shell.context_menu' | translate\">\n\n @for (section of sections(); track $index) {\n @if ($index > 0) {\n <div class=\"menu-divider\"></div>\n }\n @if (section.label) {\n <div class=\"menu-section-label\">{{ section.label }}</div>\n }\n @for (item of section.items; track item.id) {\n <button\n type=\"button\"\n class=\"vos-btn sm rect menu-item\"\n role=\"menuitem\"\n (click)=\"onAction(item.id)\">\n <i [class]=\"'pi ' + item.icon\" aria-hidden=\"true\"></i>\n <span>{{ item.label }}</span>\n </button>\n }\n }\n</div>\n", styles: [":host{display:contents}.context-menu{position:fixed;min-width:200px;padding:6px;border-radius:14px;background:var(--glass-bg, rgba(30, 30, 34, .72));backdrop-filter:blur(40px) saturate(180%);-webkit-backdrop-filter:blur(40px) saturate(180%);border:1px solid var(--glass-border, rgba(255, 255, 255, .15));box-shadow:0 12px 40px #00000059,0 2px 8px #0003,inset 0 .5px #ffffff1a;animation:menuEnter .18s cubic-bezier(.22,1,.36,1) both;transform-origin:top left}@keyframes menuEnter{0%{opacity:0;transform:scale(.92)}to{opacity:1;transform:scale(1)}}.menu-section-label{padding:6px 12px 4px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.6px;color:var(--text-color-secondary, rgba(255, 255, 255, .45));pointer-events:none}.menu-item{gap:10px;width:100%;padding:8px 12px;color:var(--text-color, #fff);font-size:var(--link-font-size, 13px);font-weight:var(--btn-symbol-font-weight, 510);justify-content:flex-start}.menu-item i{font-size:14px;width:18px;text-align:center;opacity:.7}.menu-item:hover{background:var(--primary-color, #E8732A);color:#fff}.menu-item:hover i{opacity:1}.menu-item:focus-visible{background:var(--primary-color, #E8732A);outline:2px solid var(--focus-ring);outline-offset:-2px}.menu-divider{height:1px;margin:4px 8px;background:var(--glass-border, rgba(255, 255, 255, .12))}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
768
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: ContextMenuComponent, isStandalone: true, selector: "fly-context-menu", inputs: { x: { classPropertyName: "x", publicName: "x", isSignal: true, isRequired: true, transformFunction: null }, y: { classPropertyName: "y", publicName: "y", isSignal: true, isRequired: true, transformFunction: null }, sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { action: "action", closed: "closed" }, host: { listeners: { "document:mousedown": "onClickOutside($event)", "document:keydown.escape": "onEscape()", "document:contextmenu": "onContextMenu($event)", "keydown": "onKeydown($event)" } }, viewQueries: [{ propertyName: "menuEl", first: true, predicate: ["contextMenu"], descendants: true }], ngImport: i0, template: "<div\n #contextMenu\n class=\"context-menu\"\n [style.left.px]=\"clampedPos().left\"\n [style.top.px]=\"clampedPos().top\"\n role=\"menu\"\n [attr.aria-label]=\"'shell.context_menu' | translate\">\n\n @for (section of sections(); track $index) {\n @if ($index > 0) {\n <div class=\"menu-divider\"></div>\n }\n @if (section.label) {\n <div class=\"menu-section-label\">{{ section.label }}</div>\n }\n @for (item of section.items; track item.id) {\n <button\n type=\"button\"\n class=\"vos-btn sm rect menu-item\"\n role=\"menuitem\"\n (click)=\"onAction(item.id)\">\n <i [class]=\"'pi ' + item.icon\" aria-hidden=\"true\"></i>\n <span>{{ item.label }}</span>\n </button>\n }\n }\n</div>\n", styles: [":host{display:contents}.context-menu{position:fixed;min-width:200px;padding:6px;border-radius:14px;background:var(--glass-bg, rgba(30, 30, 34, .72));-webkit-backdrop-filter:blur(40px) saturate(180%);backdrop-filter:blur(40px) saturate(180%);border:1px solid var(--glass-border, rgba(255, 255, 255, .15));box-shadow:0 12px 40px #00000059,0 2px 8px #0003,inset 0 .5px #ffffff1a;animation:menuEnter .18s cubic-bezier(.22,1,.36,1) both;transform-origin:top left}@keyframes menuEnter{0%{opacity:0;transform:scale(.92)}to{opacity:1;transform:scale(1)}}.menu-section-label{padding:6px 12px 4px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.6px;color:var(--text-color-secondary, rgba(255, 255, 255, .45));pointer-events:none}.menu-item{gap:10px;width:100%;padding:8px 12px;color:var(--text-color, #fff);font-size:var(--link-font-size, 13px);font-weight:var(--btn-symbol-font-weight, 510);justify-content:flex-start}.menu-item i{font-size:14px;width:18px;text-align:center;opacity:.7}.menu-item:hover{background:var(--primary-color, #E8732A);color:#fff}.menu-item:hover i{opacity:1}.menu-item:focus-visible{background:var(--primary-color, #E8732A);outline:2px solid var(--focus-ring);outline-offset:-2px}.menu-divider{height:1px;margin:4px 8px;background:var(--glass-border, rgba(255, 255, 255, .12))}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
623
769
  }
624
770
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ContextMenuComponent, decorators: [{
625
771
  type: Component,
626
- args: [{ selector: 'fly-context-menu', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n #contextMenu\n class=\"context-menu\"\n [style.left.px]=\"clampedPos().left\"\n [style.top.px]=\"clampedPos().top\"\n role=\"menu\"\n [attr.aria-label]=\"'shell.context_menu' | translate\">\n\n @for (section of sections(); track $index) {\n @if ($index > 0) {\n <div class=\"menu-divider\"></div>\n }\n @if (section.label) {\n <div class=\"menu-section-label\">{{ section.label }}</div>\n }\n @for (item of section.items; track item.id) {\n <button\n type=\"button\"\n class=\"vos-btn sm rect menu-item\"\n role=\"menuitem\"\n (click)=\"onAction(item.id)\">\n <i [class]=\"'pi ' + item.icon\" aria-hidden=\"true\"></i>\n <span>{{ item.label }}</span>\n </button>\n }\n }\n</div>\n", styles: [":host{display:contents}.context-menu{position:fixed;min-width:200px;padding:6px;border-radius:14px;background:var(--glass-bg, rgba(30, 30, 34, .72));backdrop-filter:blur(40px) saturate(180%);-webkit-backdrop-filter:blur(40px) saturate(180%);border:1px solid var(--glass-border, rgba(255, 255, 255, .15));box-shadow:0 12px 40px #00000059,0 2px 8px #0003,inset 0 .5px #ffffff1a;animation:menuEnter .18s cubic-bezier(.22,1,.36,1) both;transform-origin:top left}@keyframes menuEnter{0%{opacity:0;transform:scale(.92)}to{opacity:1;transform:scale(1)}}.menu-section-label{padding:6px 12px 4px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.6px;color:var(--text-color-secondary, rgba(255, 255, 255, .45));pointer-events:none}.menu-item{gap:10px;width:100%;padding:8px 12px;color:var(--text-color, #fff);font-size:var(--link-font-size, 13px);font-weight:var(--btn-symbol-font-weight, 510);justify-content:flex-start}.menu-item i{font-size:14px;width:18px;text-align:center;opacity:.7}.menu-item:hover{background:var(--primary-color, #E8732A);color:#fff}.menu-item:hover i{opacity:1}.menu-item:focus-visible{background:var(--primary-color, #E8732A);outline:2px solid var(--focus-ring);outline-offset:-2px}.menu-divider{height:1px;margin:4px 8px;background:var(--glass-border, rgba(255, 255, 255, .12))}\n"] }]
772
+ args: [{ selector: 'fly-context-menu', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n #contextMenu\n class=\"context-menu\"\n [style.left.px]=\"clampedPos().left\"\n [style.top.px]=\"clampedPos().top\"\n role=\"menu\"\n [attr.aria-label]=\"'shell.context_menu' | translate\">\n\n @for (section of sections(); track $index) {\n @if ($index > 0) {\n <div class=\"menu-divider\"></div>\n }\n @if (section.label) {\n <div class=\"menu-section-label\">{{ section.label }}</div>\n }\n @for (item of section.items; track item.id) {\n <button\n type=\"button\"\n class=\"vos-btn sm rect menu-item\"\n role=\"menuitem\"\n (click)=\"onAction(item.id)\">\n <i [class]=\"'pi ' + item.icon\" aria-hidden=\"true\"></i>\n <span>{{ item.label }}</span>\n </button>\n }\n }\n</div>\n", styles: [":host{display:contents}.context-menu{position:fixed;min-width:200px;padding:6px;border-radius:14px;background:var(--glass-bg, rgba(30, 30, 34, .72));-webkit-backdrop-filter:blur(40px) saturate(180%);backdrop-filter:blur(40px) saturate(180%);border:1px solid var(--glass-border, rgba(255, 255, 255, .15));box-shadow:0 12px 40px #00000059,0 2px 8px #0003,inset 0 .5px #ffffff1a;animation:menuEnter .18s cubic-bezier(.22,1,.36,1) both;transform-origin:top left}@keyframes menuEnter{0%{opacity:0;transform:scale(.92)}to{opacity:1;transform:scale(1)}}.menu-section-label{padding:6px 12px 4px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.6px;color:var(--text-color-secondary, rgba(255, 255, 255, .45));pointer-events:none}.menu-item{gap:10px;width:100%;padding:8px 12px;color:var(--text-color, #fff);font-size:var(--link-font-size, 13px);font-weight:var(--btn-symbol-font-weight, 510);justify-content:flex-start}.menu-item i{font-size:14px;width:18px;text-align:center;opacity:.7}.menu-item:hover{background:var(--primary-color, #E8732A);color:#fff}.menu-item:hover i{opacity:1}.menu-item:focus-visible{background:var(--primary-color, #E8732A);outline:2px solid var(--focus-ring);outline-offset:-2px}.menu-divider{height:1px;margin:4px 8px;background:var(--glass-border, rgba(255, 255, 255, .12))}\n"] }]
627
773
  }], propDecorators: { menuEl: [{
628
774
  type: ViewChild,
629
775
  args: ['contextMenu']
@@ -676,29 +822,65 @@ class MessageBoxService {
676
822
  message = signal('', ...(ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []));
677
823
  icon = signal(MessageBoxIcon.None, ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
678
824
  buttons = signal([], ...(ngDevMode ? [{ debugName: "buttons" }] : /* istanbul ignore next */ []));
825
+ /**
826
+ * Active "Don't ask again" config — populated only by `showAcknowledged()`.
827
+ * `show()` callers cannot reach this signal because the type system rejects
828
+ * `dontAskAgain` on the base `MessageBoxOptions`.
829
+ */
830
+ dontAskAgain = signal(undefined, ...(ngDevMode ? [{ debugName: "dontAskAgain" }] : /* istanbul ignore next */ []));
831
+ /**
832
+ * Component-side checkbox state. The component (`MessageBoxComponent`) wires
833
+ * its own signal to this one; the service explicitly re-seeds it on every
834
+ * `_open()` so reusing a constant `options` literal across two opens does
835
+ * NOT leak the previous user's checkbox state through signal `===` dedupe.
836
+ * Exposed for the component to read/write; not for general callers.
837
+ */
838
+ dontAskAgainChecked = signal(false, ...(ngDevMode ? [{ debugName: "dontAskAgainChecked" }] : /* istanbul ignore next */ []));
679
839
  resolver = null;
680
840
  show(options) {
681
- // Dismiss any pending dialog so its Promise resolves rather than leaking
841
+ return this._open(options).then((r) => r.result);
842
+ }
843
+ /**
844
+ * Opens a message box with a "Don't ask again" checkbox and resolves to both
845
+ * the dialog result and the checkbox state. Throws when `options.dontAskAgain`
846
+ * is missing — the checkbox config is mandatory for this entry point. The
847
+ * compile-time signature already guarantees presence; the runtime check is
848
+ * defense-in-depth for callers that bypass the type system via `as never`.
849
+ */
850
+ showAcknowledged(options) {
851
+ if (!options.dontAskAgain) {
852
+ throw new Error('MessageBoxService.showAcknowledged() requires options.dontAskAgain — use show() for plain dialogs.');
853
+ }
854
+ return this._open(options);
855
+ }
856
+ resolve(result, dontAskAgain = false) {
857
+ this.visible.set(false);
858
+ if (this.resolver) {
859
+ this.resolver({ result, dontAskAgain });
860
+ this.resolver = null;
861
+ }
862
+ }
863
+ _open(options) {
864
+ // Dismiss any pending dialog so its Promise resolves rather than leaking.
682
865
  if (this.resolver) {
683
- this.resolver(DialogResult.None);
866
+ this.resolver({ result: DialogResult.None, dontAskAgain: false });
684
867
  this.resolver = null;
685
868
  }
869
+ const daa = 'dontAskAgain' in options ? options.dontAskAgain : undefined;
686
870
  this.title.set(options.title);
687
871
  this.message.set(options.message);
688
872
  this.icon.set(options.icon ?? MessageBoxIcon.None);
689
873
  this.buttons.set(this.resolveButtons(options.buttons ?? MessageBoxButtons.OK));
874
+ this.dontAskAgain.set(daa);
875
+ // Explicit re-seed: signal `set` dedupes by `===`, so an `effect()` on
876
+ // `dontAskAgain` would not re-fire when a caller passes the SAME options
877
+ // literal twice. Set the checkbox state directly each open.
878
+ this.dontAskAgainChecked.set(daa?.defaultChecked ?? false);
690
879
  this.visible.set(true);
691
880
  return new Promise((resolve) => {
692
881
  this.resolver = resolve;
693
882
  });
694
883
  }
695
- resolve(result) {
696
- this.visible.set(false);
697
- if (this.resolver) {
698
- this.resolver(result);
699
- this.resolver = null;
700
- }
701
- }
702
884
  resolveButtons(buttons) {
703
885
  const t = (key) => this.i18n.t(key);
704
886
  switch (buttons) {
@@ -746,6 +928,19 @@ class MessageBoxComponent {
746
928
  elRef = inject(ElementRef);
747
929
  MessageBoxIcon = MessageBoxIcon;
748
930
  DialogResult = DialogResult;
931
+ /** Mirror of the service's `dontAskAgain` config so the template can read it directly. */
932
+ dontAskAgainConfig = computed(() => this.service.dontAskAgain(), ...(ngDevMode ? [{ debugName: "dontAskAgainConfig" }] : /* istanbul ignore next */ []));
933
+ /**
934
+ * User-driven checkbox state, owned by the service and re-seeded on every
935
+ * `_open()` so reusing a constant options literal cannot leak prior state.
936
+ */
937
+ dontAskAgainChecked = this.service.dontAskAgainChecked;
938
+ /**
939
+ * Concatenated id list for the dialog's `aria-describedby`. Always includes
940
+ * the message; appends the checkbox label id when the checkbox is rendered
941
+ * so screen readers announce the checkbox as part of the description.
942
+ */
943
+ ariaDescribedBy = computed(() => this.dontAskAgainConfig() ? 'mb-message mb-dont-ask' : 'mb-message', ...(ngDevMode ? [{ debugName: "ariaDescribedBy" }] : /* istanbul ignore next */ []));
749
944
  previouslyFocused = null;
750
945
  ngAfterViewInit() {
751
946
  // Store focus origin so we can restore it on close
@@ -754,18 +949,22 @@ class MessageBoxComponent {
754
949
  }
755
950
  onEscape() {
756
951
  if (this.service.visible()) {
757
- this.service.resolve(DialogResult.Cancel);
952
+ this.service.resolve(DialogResult.Cancel, this.dontAskAgainChecked());
758
953
  this.restoreFocus();
759
954
  }
760
955
  }
761
956
  onBackdropClick() {
762
- this.service.resolve(DialogResult.Cancel);
957
+ this.service.resolve(DialogResult.Cancel, this.dontAskAgainChecked());
763
958
  this.restoreFocus();
764
959
  }
765
960
  onButtonClick(result) {
766
- this.service.resolve(result);
961
+ this.service.resolve(result, this.dontAskAgainChecked());
767
962
  this.restoreFocus();
768
963
  }
964
+ onDontAskAgainToggle(ev) {
965
+ const target = ev.target;
966
+ this.dontAskAgainChecked.set(!!target?.checked);
967
+ }
769
968
  iconClass() {
770
969
  switch (this.service.icon()) {
771
970
  case MessageBoxIcon.Information: return 'pi pi-info-circle';
@@ -788,11 +987,11 @@ class MessageBoxComponent {
788
987
  this.previouslyFocused = null;
789
988
  }
790
989
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MessageBoxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
791
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: MessageBoxComponent, isStandalone: true, selector: "fly-message-box", host: { listeners: { "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: "@if (service.visible()) {\n <div\n class=\"mb-backdrop\"\n (click)=\"onBackdropClick()\"\n role=\"presentation\">\n <div\n class=\"mb-dialog\"\n [class.mb-info]=\"service.icon() === MessageBoxIcon.Information\"\n [class.mb-warning]=\"service.icon() === MessageBoxIcon.Warning\"\n [class.mb-danger]=\"service.icon() === MessageBoxIcon.Error\"\n [class.mb-question]=\"service.icon() === MessageBoxIcon.Question\"\n (click)=\"$event.stopPropagation()\"\n role=\"alertdialog\"\n aria-modal=\"true\"\n aria-labelledby=\"mb-title\"\n aria-describedby=\"mb-message\">\n\n @if (iconClass()) {\n <div class=\"mb-icon-wrap\">\n <i [class]=\"iconClass() + ' mb-icon'\" aria-hidden=\"true\"></i>\n </div>\n }\n\n <div class=\"mb-title\" id=\"mb-title\">{{ service.title() }}</div>\n <div class=\"mb-message\" id=\"mb-message\">{{ service.message() }}</div>\n\n <div class=\"mb-actions\">\n @for (btn of service.buttons(); track btn.result) {\n <button\n type=\"button\"\n class=\"vos-btn sm mb-btn\"\n [class.mb-btn--primary]=\"btn.variant === 'primary'\"\n [class.mb-btn--danger]=\"btn.variant === 'danger'\"\n (click)=\"onButtonClick(btn.result)\">\n {{ btn.label }}\n </button>\n }\n </div>\n\n </div>\n </div>\n}\n", styles: [".mb-backdrop{position:absolute;inset:0;z-index:5100;background:#00000073;display:flex;align-items:center;justify-content:center;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);animation:mbFadeIn .15s ease both}@keyframes mbFadeIn{0%{opacity:0}to{opacity:1}}.mb-dialog{background:var(--surface-card, rgba(30, 30, 30, .98));border:1px solid var(--surface-border);border-radius:16px;padding:28px 28px 20px;width:400px;max-width:90%;display:flex;flex-direction:column;align-items:center;text-align:center;gap:6px;box-shadow:0 20px 60px #0006;animation:mbScaleIn .2s cubic-bezier(.22,1,.36,1) both}@keyframes mbScaleIn{0%{opacity:0;transform:scale(.92)}to{opacity:1;transform:scale(1)}}.mb-icon-wrap{width:52px;height:52px;border-radius:50%;display:flex;align-items:center;justify-content:center;margin-bottom:6px}.mb-icon{font-size:24px}.mb-info .mb-icon-wrap{background:#3b82f61f}.mb-info .mb-icon{color:#3b82f6}.mb-warning .mb-icon-wrap{background:#f59e0b1f}.mb-warning .mb-icon{color:#f59e0b}.mb-danger .mb-icon-wrap{background:#ef44441f}.mb-danger .mb-icon{color:#ef4444}.mb-question .mb-icon-wrap{background:#8b5cf61f}.mb-question .mb-icon{color:#8b5cf6}.mb-title{font-size:15px;font-weight:700;color:var(--text-color)}.mb-message{font-size:13px;color:var(--text-color-secondary);line-height:1.55;max-width:340px;white-space:pre-line}.mb-actions{display:flex;gap:10px;margin-top:16px;width:100%;justify-content:center;flex-wrap:wrap}.mb-btn{min-width:90px}.mb-btn--primary{background:var(--primary-color, #E8732A);color:#fff}.mb-btn--primary:hover{filter:brightness(1.1)}.mb-btn--danger{background:#ef4444;color:#fff}.mb-btn--danger:hover{background:#dc2626}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
990
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: MessageBoxComponent, isStandalone: true, selector: "fly-message-box", host: { listeners: { "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: "@if (service.visible()) {\r\n <div\r\n class=\"mb-backdrop\"\r\n (click)=\"onBackdropClick()\"\r\n role=\"presentation\">\r\n <div\r\n class=\"mb-dialog\"\r\n [class.mb-info]=\"service.icon() === MessageBoxIcon.Information\"\r\n [class.mb-warning]=\"service.icon() === MessageBoxIcon.Warning\"\r\n [class.mb-danger]=\"service.icon() === MessageBoxIcon.Error\"\r\n [class.mb-question]=\"service.icon() === MessageBoxIcon.Question\"\r\n (click)=\"$event.stopPropagation()\"\r\n role=\"alertdialog\"\r\n aria-modal=\"true\"\r\n aria-labelledby=\"mb-title\"\r\n [attr.aria-describedby]=\"ariaDescribedBy()\">\r\n\r\n @if (iconClass()) {\r\n <div class=\"mb-icon-wrap\">\r\n <i [class]=\"iconClass() + ' mb-icon'\" aria-hidden=\"true\"></i>\r\n </div>\r\n }\r\n\r\n <div class=\"mb-title\" id=\"mb-title\">{{ service.title() }}</div>\r\n <div class=\"mb-message\" id=\"mb-message\">{{ service.message() }}</div>\r\n\r\n @if (dontAskAgainConfig(); as daa) {\r\n <label class=\"mb-dont-ask-again\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"dontAskAgainChecked()\"\r\n (change)=\"onDontAskAgainToggle($event)\" />\r\n <span id=\"mb-dont-ask\">{{ daa.labelKey | translate }}</span>\r\n </label>\r\n }\r\n\r\n <div class=\"mb-actions\">\r\n @for (btn of service.buttons(); track btn.result) {\r\n <button\r\n type=\"button\"\r\n class=\"vos-btn sm mb-btn\"\r\n [class.mb-btn--primary]=\"btn.variant === 'primary'\"\r\n [class.mb-btn--danger]=\"btn.variant === 'danger'\"\r\n (click)=\"onButtonClick(btn.result)\">\r\n {{ btn.label }}\r\n </button>\r\n }\r\n </div>\r\n\r\n </div>\r\n </div>\r\n}\r\n", styles: [".mb-backdrop{position:absolute;inset:0;z-index:5100;background:#00000073;display:flex;align-items:center;justify-content:center;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);animation:mbFadeIn .15s ease both}@keyframes mbFadeIn{0%{opacity:0}to{opacity:1}}.mb-dialog{background:var(--surface-card, rgba(30, 30, 30, .98));border:1px solid var(--surface-border);border-radius:16px;padding:28px 28px 20px;width:400px;max-width:90%;display:flex;flex-direction:column;align-items:center;text-align:center;gap:6px;box-shadow:0 20px 60px #0006;animation:mbScaleIn .2s cubic-bezier(.22,1,.36,1) both}@keyframes mbScaleIn{0%{opacity:0;transform:scale(.92)}to{opacity:1;transform:scale(1)}}.mb-icon-wrap{width:52px;height:52px;border-radius:50%;display:flex;align-items:center;justify-content:center;margin-bottom:6px}.mb-icon{font-size:24px}.mb-info .mb-icon-wrap{background:#3b82f61f}.mb-info .mb-icon{color:#3b82f6}.mb-warning .mb-icon-wrap{background:#f59e0b1f}.mb-warning .mb-icon{color:#f59e0b}.mb-danger .mb-icon-wrap{background:#ef44441f}.mb-danger .mb-icon{color:#ef4444}.mb-question .mb-icon-wrap{background:#8b5cf61f}.mb-question .mb-icon{color:#8b5cf6}.mb-title{font-size:15px;font-weight:700;color:var(--text-color)}.mb-message{font-size:13px;color:var(--text-color-secondary);line-height:1.55;max-width:340px;white-space:pre-line}.mb-dont-ask-again{display:flex;align-items:center;gap:8px;margin-block-start:12px;font-size:12px;color:var(--text-color-secondary);cursor:pointer;align-self:flex-start;-webkit-user-select:none;user-select:none}.mb-dont-ask-again input[type=checkbox]{margin:0;cursor:pointer}.mb-dont-ask-again span{line-height:1.3}.mb-actions{display:flex;gap:10px;margin-top:16px;width:100%;justify-content:center;flex-wrap:wrap}.mb-btn{min-width:90px}.mb-btn--primary{background:var(--primary-color, #E8732A);color:#fff}.mb-btn--primary:hover{filter:brightness(1.1)}.mb-btn--danger{background:#ef4444;color:#fff}.mb-btn--danger:hover{background:#dc2626}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
792
991
  }
793
992
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MessageBoxComponent, decorators: [{
794
993
  type: Component,
795
- args: [{ selector: 'fly-message-box', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (service.visible()) {\n <div\n class=\"mb-backdrop\"\n (click)=\"onBackdropClick()\"\n role=\"presentation\">\n <div\n class=\"mb-dialog\"\n [class.mb-info]=\"service.icon() === MessageBoxIcon.Information\"\n [class.mb-warning]=\"service.icon() === MessageBoxIcon.Warning\"\n [class.mb-danger]=\"service.icon() === MessageBoxIcon.Error\"\n [class.mb-question]=\"service.icon() === MessageBoxIcon.Question\"\n (click)=\"$event.stopPropagation()\"\n role=\"alertdialog\"\n aria-modal=\"true\"\n aria-labelledby=\"mb-title\"\n aria-describedby=\"mb-message\">\n\n @if (iconClass()) {\n <div class=\"mb-icon-wrap\">\n <i [class]=\"iconClass() + ' mb-icon'\" aria-hidden=\"true\"></i>\n </div>\n }\n\n <div class=\"mb-title\" id=\"mb-title\">{{ service.title() }}</div>\n <div class=\"mb-message\" id=\"mb-message\">{{ service.message() }}</div>\n\n <div class=\"mb-actions\">\n @for (btn of service.buttons(); track btn.result) {\n <button\n type=\"button\"\n class=\"vos-btn sm mb-btn\"\n [class.mb-btn--primary]=\"btn.variant === 'primary'\"\n [class.mb-btn--danger]=\"btn.variant === 'danger'\"\n (click)=\"onButtonClick(btn.result)\">\n {{ btn.label }}\n </button>\n }\n </div>\n\n </div>\n </div>\n}\n", styles: [".mb-backdrop{position:absolute;inset:0;z-index:5100;background:#00000073;display:flex;align-items:center;justify-content:center;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);animation:mbFadeIn .15s ease both}@keyframes mbFadeIn{0%{opacity:0}to{opacity:1}}.mb-dialog{background:var(--surface-card, rgba(30, 30, 30, .98));border:1px solid var(--surface-border);border-radius:16px;padding:28px 28px 20px;width:400px;max-width:90%;display:flex;flex-direction:column;align-items:center;text-align:center;gap:6px;box-shadow:0 20px 60px #0006;animation:mbScaleIn .2s cubic-bezier(.22,1,.36,1) both}@keyframes mbScaleIn{0%{opacity:0;transform:scale(.92)}to{opacity:1;transform:scale(1)}}.mb-icon-wrap{width:52px;height:52px;border-radius:50%;display:flex;align-items:center;justify-content:center;margin-bottom:6px}.mb-icon{font-size:24px}.mb-info .mb-icon-wrap{background:#3b82f61f}.mb-info .mb-icon{color:#3b82f6}.mb-warning .mb-icon-wrap{background:#f59e0b1f}.mb-warning .mb-icon{color:#f59e0b}.mb-danger .mb-icon-wrap{background:#ef44441f}.mb-danger .mb-icon{color:#ef4444}.mb-question .mb-icon-wrap{background:#8b5cf61f}.mb-question .mb-icon{color:#8b5cf6}.mb-title{font-size:15px;font-weight:700;color:var(--text-color)}.mb-message{font-size:13px;color:var(--text-color-secondary);line-height:1.55;max-width:340px;white-space:pre-line}.mb-actions{display:flex;gap:10px;margin-top:16px;width:100%;justify-content:center;flex-wrap:wrap}.mb-btn{min-width:90px}.mb-btn--primary{background:var(--primary-color, #E8732A);color:#fff}.mb-btn--primary:hover{filter:brightness(1.1)}.mb-btn--danger{background:#ef4444;color:#fff}.mb-btn--danger:hover{background:#dc2626}\n"] }]
994
+ args: [{ selector: 'fly-message-box', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (service.visible()) {\r\n <div\r\n class=\"mb-backdrop\"\r\n (click)=\"onBackdropClick()\"\r\n role=\"presentation\">\r\n <div\r\n class=\"mb-dialog\"\r\n [class.mb-info]=\"service.icon() === MessageBoxIcon.Information\"\r\n [class.mb-warning]=\"service.icon() === MessageBoxIcon.Warning\"\r\n [class.mb-danger]=\"service.icon() === MessageBoxIcon.Error\"\r\n [class.mb-question]=\"service.icon() === MessageBoxIcon.Question\"\r\n (click)=\"$event.stopPropagation()\"\r\n role=\"alertdialog\"\r\n aria-modal=\"true\"\r\n aria-labelledby=\"mb-title\"\r\n [attr.aria-describedby]=\"ariaDescribedBy()\">\r\n\r\n @if (iconClass()) {\r\n <div class=\"mb-icon-wrap\">\r\n <i [class]=\"iconClass() + ' mb-icon'\" aria-hidden=\"true\"></i>\r\n </div>\r\n }\r\n\r\n <div class=\"mb-title\" id=\"mb-title\">{{ service.title() }}</div>\r\n <div class=\"mb-message\" id=\"mb-message\">{{ service.message() }}</div>\r\n\r\n @if (dontAskAgainConfig(); as daa) {\r\n <label class=\"mb-dont-ask-again\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"dontAskAgainChecked()\"\r\n (change)=\"onDontAskAgainToggle($event)\" />\r\n <span id=\"mb-dont-ask\">{{ daa.labelKey | translate }}</span>\r\n </label>\r\n }\r\n\r\n <div class=\"mb-actions\">\r\n @for (btn of service.buttons(); track btn.result) {\r\n <button\r\n type=\"button\"\r\n class=\"vos-btn sm mb-btn\"\r\n [class.mb-btn--primary]=\"btn.variant === 'primary'\"\r\n [class.mb-btn--danger]=\"btn.variant === 'danger'\"\r\n (click)=\"onButtonClick(btn.result)\">\r\n {{ btn.label }}\r\n </button>\r\n }\r\n </div>\r\n\r\n </div>\r\n </div>\r\n}\r\n", styles: [".mb-backdrop{position:absolute;inset:0;z-index:5100;background:#00000073;display:flex;align-items:center;justify-content:center;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);animation:mbFadeIn .15s ease both}@keyframes mbFadeIn{0%{opacity:0}to{opacity:1}}.mb-dialog{background:var(--surface-card, rgba(30, 30, 30, .98));border:1px solid var(--surface-border);border-radius:16px;padding:28px 28px 20px;width:400px;max-width:90%;display:flex;flex-direction:column;align-items:center;text-align:center;gap:6px;box-shadow:0 20px 60px #0006;animation:mbScaleIn .2s cubic-bezier(.22,1,.36,1) both}@keyframes mbScaleIn{0%{opacity:0;transform:scale(.92)}to{opacity:1;transform:scale(1)}}.mb-icon-wrap{width:52px;height:52px;border-radius:50%;display:flex;align-items:center;justify-content:center;margin-bottom:6px}.mb-icon{font-size:24px}.mb-info .mb-icon-wrap{background:#3b82f61f}.mb-info .mb-icon{color:#3b82f6}.mb-warning .mb-icon-wrap{background:#f59e0b1f}.mb-warning .mb-icon{color:#f59e0b}.mb-danger .mb-icon-wrap{background:#ef44441f}.mb-danger .mb-icon{color:#ef4444}.mb-question .mb-icon-wrap{background:#8b5cf61f}.mb-question .mb-icon{color:#8b5cf6}.mb-title{font-size:15px;font-weight:700;color:var(--text-color)}.mb-message{font-size:13px;color:var(--text-color-secondary);line-height:1.55;max-width:340px;white-space:pre-line}.mb-dont-ask-again{display:flex;align-items:center;gap:8px;margin-block-start:12px;font-size:12px;color:var(--text-color-secondary);cursor:pointer;align-self:flex-start;-webkit-user-select:none;user-select:none}.mb-dont-ask-again input[type=checkbox]{margin:0;cursor:pointer}.mb-dont-ask-again span{line-height:1.3}.mb-actions{display:flex;gap:10px;margin-top:16px;width:100%;justify-content:center;flex-wrap:wrap}.mb-btn{min-width:90px}.mb-btn--primary{background:var(--primary-color, #E8732A);color:#fff}.mb-btn--primary:hover{filter:brightness(1.1)}.mb-btn--danger{background:#ef4444;color:#fff}.mb-btn--danger:hover{background:#dc2626}\n"] }]
796
995
  }], propDecorators: { onEscape: [{
797
996
  type: HostListener,
798
997
  args: ['document:keydown.escape']
@@ -1832,11 +2031,11 @@ class FlyBlockUiComponent {
1832
2031
  return k && k.length > 0 ? k : 'common.loading';
1833
2032
  }, ...(ngDevMode ? [{ debugName: "resolvedMessageKey" }] : /* istanbul ignore next */ []));
1834
2033
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyBlockUiComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1835
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: FlyBlockUiComponent, isStandalone: true, selector: "fly-block-ui", inputs: { active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: true, transformFunction: null }, messageKey: { classPropertyName: "messageKey", publicName: "messageKey", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (active()) {\n <div\n class=\"fly-block-ui\"\n role=\"status\"\n aria-live=\"polite\"\n aria-busy=\"true\"\n [attr.aria-label]=\"resolvedMessageKey() | translate\">\n <div class=\"fly-block-ui__card\">\n <i class=\"pi pi-spin pi-spinner fly-block-ui__spinner\" aria-hidden=\"true\"></i>\n <span class=\"fly-block-ui__text\">{{ resolvedMessageKey() | translate }}</span>\n </div>\n </div>\n}\n", styles: [":host{display:contents}.fly-block-ui{position:absolute;inset:0;z-index:50;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--surface-ground, #0c0c12) 58%,transparent);backdrop-filter:blur(6px) saturate(120%);-webkit-backdrop-filter:blur(6px) saturate(120%)}.fly-block-ui__card{display:flex;flex-direction:column;align-items:center;gap:12px;padding:24px 36px;border-radius:12px;background:var(--glass-bg, rgba(255, 255, 255, .14));border:1px solid var(--glass-border, rgba(255, 255, 255, .22));box-shadow:0 8px 32px #0000002e}.fly-block-ui__spinner{font-size:2rem;color:var(--primary-color)}.fly-block-ui__text{font-size:.875rem;color:var(--text-color-secondary)}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2034
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: FlyBlockUiComponent, isStandalone: true, selector: "fly-block-ui", inputs: { active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: true, transformFunction: null }, messageKey: { classPropertyName: "messageKey", publicName: "messageKey", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (active()) {\n <div\n class=\"fly-block-ui\"\n role=\"status\"\n aria-live=\"polite\"\n aria-busy=\"true\"\n [attr.aria-label]=\"resolvedMessageKey() | translate\">\n <div class=\"fly-block-ui__card\">\n <i class=\"pi pi-spin pi-spinner fly-block-ui__spinner\" aria-hidden=\"true\"></i>\n <span class=\"fly-block-ui__text\">{{ resolvedMessageKey() | translate }}</span>\n </div>\n </div>\n}\n", styles: [":host{display:contents}.fly-block-ui{position:absolute;inset:0;z-index:50;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--surface-ground, #0c0c12) 58%,transparent);-webkit-backdrop-filter:blur(6px) saturate(120%);backdrop-filter:blur(6px) saturate(120%)}.fly-block-ui__card{display:flex;flex-direction:column;align-items:center;gap:12px;padding:24px 36px;border-radius:12px;background:var(--glass-bg, rgba(255, 255, 255, .14));border:1px solid var(--glass-border, rgba(255, 255, 255, .22));box-shadow:0 8px 32px #0000002e}.fly-block-ui__spinner{font-size:2rem;color:var(--primary-color)}.fly-block-ui__text{font-size:.875rem;color:var(--text-color-secondary)}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1836
2035
  }
1837
2036
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyBlockUiComponent, decorators: [{
1838
2037
  type: Component,
1839
- args: [{ selector: 'fly-block-ui', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (active()) {\n <div\n class=\"fly-block-ui\"\n role=\"status\"\n aria-live=\"polite\"\n aria-busy=\"true\"\n [attr.aria-label]=\"resolvedMessageKey() | translate\">\n <div class=\"fly-block-ui__card\">\n <i class=\"pi pi-spin pi-spinner fly-block-ui__spinner\" aria-hidden=\"true\"></i>\n <span class=\"fly-block-ui__text\">{{ resolvedMessageKey() | translate }}</span>\n </div>\n </div>\n}\n", styles: [":host{display:contents}.fly-block-ui{position:absolute;inset:0;z-index:50;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--surface-ground, #0c0c12) 58%,transparent);backdrop-filter:blur(6px) saturate(120%);-webkit-backdrop-filter:blur(6px) saturate(120%)}.fly-block-ui__card{display:flex;flex-direction:column;align-items:center;gap:12px;padding:24px 36px;border-radius:12px;background:var(--glass-bg, rgba(255, 255, 255, .14));border:1px solid var(--glass-border, rgba(255, 255, 255, .22));box-shadow:0 8px 32px #0000002e}.fly-block-ui__spinner{font-size:2rem;color:var(--primary-color)}.fly-block-ui__text{font-size:.875rem;color:var(--text-color-secondary)}\n"] }]
2038
+ args: [{ selector: 'fly-block-ui', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (active()) {\n <div\n class=\"fly-block-ui\"\n role=\"status\"\n aria-live=\"polite\"\n aria-busy=\"true\"\n [attr.aria-label]=\"resolvedMessageKey() | translate\">\n <div class=\"fly-block-ui__card\">\n <i class=\"pi pi-spin pi-spinner fly-block-ui__spinner\" aria-hidden=\"true\"></i>\n <span class=\"fly-block-ui__text\">{{ resolvedMessageKey() | translate }}</span>\n </div>\n </div>\n}\n", styles: [":host{display:contents}.fly-block-ui{position:absolute;inset:0;z-index:50;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--surface-ground, #0c0c12) 58%,transparent);-webkit-backdrop-filter:blur(6px) saturate(120%);backdrop-filter:blur(6px) saturate(120%)}.fly-block-ui__card{display:flex;flex-direction:column;align-items:center;gap:12px;padding:24px 36px;border-radius:12px;background:var(--glass-bg, rgba(255, 255, 255, .14));border:1px solid var(--glass-border, rgba(255, 255, 255, .22));box-shadow:0 8px 32px #0000002e}.fly-block-ui__spinner{font-size:2rem;color:var(--primary-color)}.fly-block-ui__text{font-size:.875rem;color:var(--text-color-secondary)}\n"] }]
1840
2039
  }], propDecorators: { active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: true }] }], messageKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "messageKey", required: false }] }] } });
1841
2040
 
1842
2041
  /**
@@ -2017,110 +2216,110 @@ class FlyImageUploadComponent {
2017
2216
  });
2018
2217
  }
2019
2218
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyImageUploadComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2020
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: FlyImageUploadComponent, isStandalone: true, selector: "fly-image-upload", inputs: { aspectRatio: { classPropertyName: "aspectRatio", publicName: "aspectRatio", isSignal: true, isRequired: false, transformFunction: null }, maxSizeBytes: { classPropertyName: "maxSizeBytes", publicName: "maxSizeBytes", isSignal: true, isRequired: false, transformFunction: null }, currentImageId: { classPropertyName: "currentImageId", publicName: "currentImageId", 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 } }, outputs: { uploaded: "uploaded", removed: "removed" }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true, isSignal: true }, { propertyName: "cropImage", first: true, predicate: ["cropImage"], descendants: true, isSignal: true }], ngImport: i0, template: `
2021
- <div class="fly-image-upload">
2022
- @if (previewUrl()) {
2023
- <div class="fly-image-upload__preview">
2024
- <img [src]="previewUrl()" alt="" class="fly-image-upload__img" />
2025
- <div class="fly-image-upload__overlay">
2026
- <button type="button" class="fly-image-upload__action-btn" (click)="triggerFileInput()" [title]="'files.imageUpload.change' | translate">
2027
- <span class="pi pi-pencil" aria-hidden="true"></span>
2028
- </button>
2029
- <button type="button" class="fly-image-upload__action-btn fly-image-upload__action-btn--danger" (click)="removeImage()" [title]="'files.imageUpload.remove' | translate">
2030
- <span class="pi pi-trash" aria-hidden="true"></span>
2031
- </button>
2032
- </div>
2033
- </div>
2034
- } @else {
2035
- <button type="button" class="fly-image-upload__dropzone" (click)="triggerFileInput()" (dragover)="onDragOver($event)" (drop)="onDrop($event)">
2036
- @if (uploading()) {
2037
- <span class="pi pi-spin pi-spinner fly-image-upload__icon" aria-hidden="true"></span>
2038
- <span class="fly-image-upload__label">{{ 'files.imageUpload.uploading' | translate }}</span>
2039
- } @else {
2040
- <span class="pi pi-image fly-image-upload__icon" aria-hidden="true"></span>
2041
- <span class="fly-image-upload__label">{{ 'files.imageUpload.placeholder' | translate }}</span>
2042
- <span class="fly-image-upload__hint">{{ sizeHint() }}</span>
2043
- }
2044
- </button>
2045
- }
2046
-
2047
- @if (error()) {
2048
- <div class="fly-image-upload__error">{{ error() }}</div>
2049
- }
2050
-
2051
- <input #fileInput type="file" accept="image/*" class="fly-image-upload__hidden" (change)="onFileSelected($event)" />
2052
-
2053
- @if (showCropper()) {
2054
- <div class="fly-image-upload__crop-backdrop" (click)="cancelCrop()">
2055
- <div class="fly-image-upload__crop-modal" (click)="$event.stopPropagation()">
2056
- <div class="fly-image-upload__crop-header">
2057
- <h3>{{ 'files.imageUpload.crop' | translate }}</h3>
2058
- </div>
2059
- <div class="fly-image-upload__crop-body">
2060
- <img #cropImage [src]="rawImageUrl()" alt="" />
2061
- </div>
2062
- <div class="fly-image-upload__crop-footer">
2063
- <button type="button" class="vos-btn sm platter" (click)="cancelCrop()">{{ 'common.cancel' | translate }}</button>
2064
- <button type="button" class="vos-btn sm primary" (click)="applyCrop()">{{ 'files.imageUpload.apply' | translate }}</button>
2065
- </div>
2066
- </div>
2067
- </div>
2068
- }
2069
- </div>
2219
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: FlyImageUploadComponent, isStandalone: true, selector: "fly-image-upload", inputs: { aspectRatio: { classPropertyName: "aspectRatio", publicName: "aspectRatio", isSignal: true, isRequired: false, transformFunction: null }, maxSizeBytes: { classPropertyName: "maxSizeBytes", publicName: "maxSizeBytes", isSignal: true, isRequired: false, transformFunction: null }, currentImageId: { classPropertyName: "currentImageId", publicName: "currentImageId", 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 } }, outputs: { uploaded: "uploaded", removed: "removed" }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true, isSignal: true }, { propertyName: "cropImage", first: true, predicate: ["cropImage"], descendants: true, isSignal: true }], ngImport: i0, template: `
2220
+ <div class="fly-image-upload">
2221
+ @if (previewUrl()) {
2222
+ <div class="fly-image-upload__preview">
2223
+ <img [src]="previewUrl()" alt="" class="fly-image-upload__img" />
2224
+ <div class="fly-image-upload__overlay">
2225
+ <button type="button" class="fly-image-upload__action-btn" (click)="triggerFileInput()" [title]="'files.imageUpload.change' | translate">
2226
+ <span class="pi pi-pencil" aria-hidden="true"></span>
2227
+ </button>
2228
+ <button type="button" class="fly-image-upload__action-btn fly-image-upload__action-btn--danger" (click)="removeImage()" [title]="'files.imageUpload.remove' | translate">
2229
+ <span class="pi pi-trash" aria-hidden="true"></span>
2230
+ </button>
2231
+ </div>
2232
+ </div>
2233
+ } @else {
2234
+ <button type="button" class="fly-image-upload__dropzone" (click)="triggerFileInput()" (dragover)="onDragOver($event)" (drop)="onDrop($event)">
2235
+ @if (uploading()) {
2236
+ <span class="pi pi-spin pi-spinner fly-image-upload__icon" aria-hidden="true"></span>
2237
+ <span class="fly-image-upload__label">{{ 'files.imageUpload.uploading' | translate }}</span>
2238
+ } @else {
2239
+ <span class="pi pi-image fly-image-upload__icon" aria-hidden="true"></span>
2240
+ <span class="fly-image-upload__label">{{ 'files.imageUpload.placeholder' | translate }}</span>
2241
+ <span class="fly-image-upload__hint">{{ sizeHint() }}</span>
2242
+ }
2243
+ </button>
2244
+ }
2245
+
2246
+ @if (error()) {
2247
+ <div class="fly-image-upload__error">{{ error() }}</div>
2248
+ }
2249
+
2250
+ <input #fileInput type="file" accept="image/*" class="fly-image-upload__hidden" (change)="onFileSelected($event)" />
2251
+
2252
+ @if (showCropper()) {
2253
+ <div class="fly-image-upload__crop-backdrop" (click)="cancelCrop()">
2254
+ <div class="fly-image-upload__crop-modal" (click)="$event.stopPropagation()">
2255
+ <div class="fly-image-upload__crop-header">
2256
+ <h3>{{ 'files.imageUpload.crop' | translate }}</h3>
2257
+ </div>
2258
+ <div class="fly-image-upload__crop-body">
2259
+ <img #cropImage [src]="rawImageUrl()" alt="" />
2260
+ </div>
2261
+ <div class="fly-image-upload__crop-footer">
2262
+ <button type="button" class="vos-btn sm platter" (click)="cancelCrop()">{{ 'common.cancel' | translate }}</button>
2263
+ <button type="button" class="vos-btn sm primary" (click)="applyCrop()">{{ 'files.imageUpload.apply' | translate }}</button>
2264
+ </div>
2265
+ </div>
2266
+ </div>
2267
+ }
2268
+ </div>
2070
2269
  `, 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 });
2071
2270
  }
2072
2271
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyImageUploadComponent, decorators: [{
2073
2272
  type: Component,
2074
- args: [{ selector: 'fly-image-upload', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: `
2075
- <div class="fly-image-upload">
2076
- @if (previewUrl()) {
2077
- <div class="fly-image-upload__preview">
2078
- <img [src]="previewUrl()" alt="" class="fly-image-upload__img" />
2079
- <div class="fly-image-upload__overlay">
2080
- <button type="button" class="fly-image-upload__action-btn" (click)="triggerFileInput()" [title]="'files.imageUpload.change' | translate">
2081
- <span class="pi pi-pencil" aria-hidden="true"></span>
2082
- </button>
2083
- <button type="button" class="fly-image-upload__action-btn fly-image-upload__action-btn--danger" (click)="removeImage()" [title]="'files.imageUpload.remove' | translate">
2084
- <span class="pi pi-trash" aria-hidden="true"></span>
2085
- </button>
2086
- </div>
2087
- </div>
2088
- } @else {
2089
- <button type="button" class="fly-image-upload__dropzone" (click)="triggerFileInput()" (dragover)="onDragOver($event)" (drop)="onDrop($event)">
2090
- @if (uploading()) {
2091
- <span class="pi pi-spin pi-spinner fly-image-upload__icon" aria-hidden="true"></span>
2092
- <span class="fly-image-upload__label">{{ 'files.imageUpload.uploading' | translate }}</span>
2093
- } @else {
2094
- <span class="pi pi-image fly-image-upload__icon" aria-hidden="true"></span>
2095
- <span class="fly-image-upload__label">{{ 'files.imageUpload.placeholder' | translate }}</span>
2096
- <span class="fly-image-upload__hint">{{ sizeHint() }}</span>
2097
- }
2098
- </button>
2099
- }
2100
-
2101
- @if (error()) {
2102
- <div class="fly-image-upload__error">{{ error() }}</div>
2103
- }
2104
-
2105
- <input #fileInput type="file" accept="image/*" class="fly-image-upload__hidden" (change)="onFileSelected($event)" />
2106
-
2107
- @if (showCropper()) {
2108
- <div class="fly-image-upload__crop-backdrop" (click)="cancelCrop()">
2109
- <div class="fly-image-upload__crop-modal" (click)="$event.stopPropagation()">
2110
- <div class="fly-image-upload__crop-header">
2111
- <h3>{{ 'files.imageUpload.crop' | translate }}</h3>
2112
- </div>
2113
- <div class="fly-image-upload__crop-body">
2114
- <img #cropImage [src]="rawImageUrl()" alt="" />
2115
- </div>
2116
- <div class="fly-image-upload__crop-footer">
2117
- <button type="button" class="vos-btn sm platter" (click)="cancelCrop()">{{ 'common.cancel' | translate }}</button>
2118
- <button type="button" class="vos-btn sm primary" (click)="applyCrop()">{{ 'files.imageUpload.apply' | translate }}</button>
2119
- </div>
2120
- </div>
2121
- </div>
2122
- }
2123
- </div>
2273
+ args: [{ selector: 'fly-image-upload', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: `
2274
+ <div class="fly-image-upload">
2275
+ @if (previewUrl()) {
2276
+ <div class="fly-image-upload__preview">
2277
+ <img [src]="previewUrl()" alt="" class="fly-image-upload__img" />
2278
+ <div class="fly-image-upload__overlay">
2279
+ <button type="button" class="fly-image-upload__action-btn" (click)="triggerFileInput()" [title]="'files.imageUpload.change' | translate">
2280
+ <span class="pi pi-pencil" aria-hidden="true"></span>
2281
+ </button>
2282
+ <button type="button" class="fly-image-upload__action-btn fly-image-upload__action-btn--danger" (click)="removeImage()" [title]="'files.imageUpload.remove' | translate">
2283
+ <span class="pi pi-trash" aria-hidden="true"></span>
2284
+ </button>
2285
+ </div>
2286
+ </div>
2287
+ } @else {
2288
+ <button type="button" class="fly-image-upload__dropzone" (click)="triggerFileInput()" (dragover)="onDragOver($event)" (drop)="onDrop($event)">
2289
+ @if (uploading()) {
2290
+ <span class="pi pi-spin pi-spinner fly-image-upload__icon" aria-hidden="true"></span>
2291
+ <span class="fly-image-upload__label">{{ 'files.imageUpload.uploading' | translate }}</span>
2292
+ } @else {
2293
+ <span class="pi pi-image fly-image-upload__icon" aria-hidden="true"></span>
2294
+ <span class="fly-image-upload__label">{{ 'files.imageUpload.placeholder' | translate }}</span>
2295
+ <span class="fly-image-upload__hint">{{ sizeHint() }}</span>
2296
+ }
2297
+ </button>
2298
+ }
2299
+
2300
+ @if (error()) {
2301
+ <div class="fly-image-upload__error">{{ error() }}</div>
2302
+ }
2303
+
2304
+ <input #fileInput type="file" accept="image/*" class="fly-image-upload__hidden" (change)="onFileSelected($event)" />
2305
+
2306
+ @if (showCropper()) {
2307
+ <div class="fly-image-upload__crop-backdrop" (click)="cancelCrop()">
2308
+ <div class="fly-image-upload__crop-modal" (click)="$event.stopPropagation()">
2309
+ <div class="fly-image-upload__crop-header">
2310
+ <h3>{{ 'files.imageUpload.crop' | translate }}</h3>
2311
+ </div>
2312
+ <div class="fly-image-upload__crop-body">
2313
+ <img #cropImage [src]="rawImageUrl()" alt="" />
2314
+ </div>
2315
+ <div class="fly-image-upload__crop-footer">
2316
+ <button type="button" class="vos-btn sm platter" (click)="cancelCrop()">{{ 'common.cancel' | translate }}</button>
2317
+ <button type="button" class="vos-btn sm primary" (click)="applyCrop()">{{ 'files.imageUpload.apply' | translate }}</button>
2318
+ </div>
2319
+ </div>
2320
+ </div>
2321
+ }
2322
+ </div>
2124
2323
  `, 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"] }]
2125
2324
  }], 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 }] }] } });
2126
2325
 
@@ -2456,6 +2655,1113 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
2456
2655
  `, 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"] }]
2457
2656
  }], 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 }] }] } });
2458
2657
 
2658
+ /**
2659
+ * Singleton registry of slash commands offered by the agent input palette.
2660
+ *
2661
+ * Lives in the DS so it crosses the federation boundary as a single instance via
2662
+ * `sharedMappings: ['@mohamedatia/fly-design-system']`. Federated remotes register
2663
+ * their commands at boot (and dispose on window close) without forking the shell.
2664
+ *
2665
+ * Storage is a signal store. `register` is O(1) for the unique-id case and O(n) when
2666
+ * replacing an existing id (filter then push). The append-then-replace strategy is
2667
+ * deliberate: registrations are rare (each one corresponds to a remote's app-init
2668
+ * effect), and the tradeoff buys us a stable id-collision contract — the *latest*
2669
+ * registration wins, and the previous handle's `dispose()` becomes a no-op rather
2670
+ * than removing the new entry.
2671
+ */
2672
+ class AgentCommandRegistry {
2673
+ _commands = signal([], ...(ngDevMode ? [{ debugName: "_commands" }] : /* istanbul ignore next */ []));
2674
+ /** All currently-registered commands, in insertion order. */
2675
+ all = this._commands.asReadonly();
2676
+ /**
2677
+ * Returns a signal of commands whose scope is `'global'` OR whose `scope.appId` is in
2678
+ * the live app set. The signal recomputes when either the registry or `liveAppIds`
2679
+ * changes — pass a `Signal<ReadonlySet<string>>` from the host's app-registry service
2680
+ * for reactive filtering.
2681
+ */
2682
+ visible(liveAppIds) {
2683
+ const liveSignal = isSignal(liveAppIds) ? liveAppIds : signal(liveAppIds).asReadonly();
2684
+ return computed(() => {
2685
+ const live = liveSignal();
2686
+ return this._commands().filter((cmd) => {
2687
+ if (cmd.scope === 'global')
2688
+ return true;
2689
+ return live.has(cmd.scope.appId);
2690
+ });
2691
+ });
2692
+ }
2693
+ /**
2694
+ * Register a single command. Returns a handle whose `dispose()` removes the row by
2695
+ * id. If the same id is later re-registered, the original handle's `dispose()`
2696
+ * becomes a no-op (the newer registration owns the row). Idempotent disposal.
2697
+ */
2698
+ register(cmd) {
2699
+ const generation = ++this._generation;
2700
+ this._commands.update((rows) => [...rows.filter((r) => r.id !== cmd.id), cmd]);
2701
+ this._owners.set(cmd.id, generation);
2702
+ return {
2703
+ dispose: () => {
2704
+ if (this._owners.get(cmd.id) === generation) {
2705
+ this._owners.delete(cmd.id);
2706
+ this._commands.update((rows) => rows.filter((r) => r.id !== cmd.id));
2707
+ }
2708
+ },
2709
+ };
2710
+ }
2711
+ /**
2712
+ * Bulk register. Rolls back on duplicate id within the input batch (throws before any
2713
+ * row lands). Cross-batch duplicates against existing rows follow the standard
2714
+ * "latest wins" rule and do NOT trigger rollback.
2715
+ *
2716
+ * Returns a handle whose `dispose()` tears down every row registered by this call.
2717
+ */
2718
+ registerAll(cmds) {
2719
+ const seen = new Set();
2720
+ for (const c of cmds) {
2721
+ if (seen.has(c.id)) {
2722
+ throw new Error(`AgentCommandRegistry.registerAll: duplicate id "${c.id}" in batch`);
2723
+ }
2724
+ seen.add(c.id);
2725
+ }
2726
+ const handles = cmds.map((c) => this.register(c));
2727
+ let disposed = false;
2728
+ return {
2729
+ dispose: () => {
2730
+ if (disposed)
2731
+ return;
2732
+ disposed = true;
2733
+ for (const h of handles)
2734
+ h.dispose();
2735
+ },
2736
+ };
2737
+ }
2738
+ /** Tear down by id. Idempotent. */
2739
+ unregister(id) {
2740
+ if (this._owners.delete(id)) {
2741
+ this._commands.update((rows) => rows.filter((r) => r.id !== id));
2742
+ }
2743
+ }
2744
+ /** Monotonic counter; identifies which registration call currently owns each id. */
2745
+ _generation = 0;
2746
+ /** id → generation. Used so a stale handle's `dispose()` is a no-op after replacement. */
2747
+ _owners = new Map();
2748
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AgentCommandRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2749
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AgentCommandRegistry, providedIn: 'root' });
2750
+ }
2751
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AgentCommandRegistry, decorators: [{
2752
+ type: Injectable,
2753
+ args: [{ providedIn: 'root' }]
2754
+ }] });
2755
+ /** `isSignal` shim — narrows to either `Signal<T>` or a plain value. */
2756
+ function isSignal(v) {
2757
+ return typeof v === 'function';
2758
+ }
2759
+
2760
+ /**
2761
+ * Singleton registry of chip-renderer components and keyboard-alternative draggable
2762
+ * items, keyed by `kind` and `appId`.
2763
+ *
2764
+ * Like {@link AgentCommandRegistry}, this lives in the DS so it crosses the federation
2765
+ * boundary as a single instance. Hosts (the shell `<fly-agent-input>`) lookup
2766
+ * renderers; remotes register them.
2767
+ *
2768
+ * Renderer lookup is `O(n)` over the registered list — the registry is small (one or
2769
+ * two entries per app) and lookups happen on drop, not per frame.
2770
+ *
2771
+ * Draggable storage is per-`appId` writable signal cached in a Map, so each `appId`
2772
+ * gets a stable {@link Signal} reference across reads (callers can `===`-compare).
2773
+ */
2774
+ class AgentDropRegistry {
2775
+ _renderers = signal([], ...(ngDevMode ? [{ debugName: "_renderers" }] : /* istanbul ignore next */ []));
2776
+ /** Per-appId writable store for draggables. Read-only mirror returned to callers. */
2777
+ _draggablesByApp = new Map();
2778
+ /**
2779
+ * Look up the chip-renderer component class for a `kind`. The newest registration for
2780
+ * a given `kind` wins, regardless of `appId` — we scan the list in reverse so a later
2781
+ * `register` call shadows an earlier one for the same `kind`.
2782
+ *
2783
+ * Returns `null` when no renderer is registered; the host falls back to a generic
2784
+ * `plainTextFallback` chip.
2785
+ */
2786
+ rendererFor(kind) {
2787
+ const list = this._renderers();
2788
+ for (let i = list.length - 1; i >= 0; i--) {
2789
+ if (list[i].kind === kind)
2790
+ return list[i].component;
2791
+ }
2792
+ return null;
2793
+ }
2794
+ /**
2795
+ * Register a renderer for a `(kind, appId)` pair. Re-registering the same pair
2796
+ * replaces the prior entry; the disposal handle for the prior registration becomes
2797
+ * a no-op.
2798
+ *
2799
+ * Returns a {@link AgentCommandHandle} (re-used to keep the disposable shape uniform
2800
+ * across registries) whose `dispose()` removes this exact registration.
2801
+ */
2802
+ register(reg) {
2803
+ const generation = ++this._generation;
2804
+ const key = compositeKey(reg.kind, reg.appId);
2805
+ this._renderers.update((rows) => [
2806
+ ...rows.filter((r) => compositeKey(r.kind, r.appId) !== key),
2807
+ reg,
2808
+ ]);
2809
+ this._owners.set(key, generation);
2810
+ return {
2811
+ dispose: () => {
2812
+ if (this._owners.get(key) === generation) {
2813
+ this._owners.delete(key);
2814
+ this._renderers.update((rows) => rows.filter((r) => compositeKey(r.kind, r.appId) !== key));
2815
+ }
2816
+ },
2817
+ };
2818
+ }
2819
+ /**
2820
+ * Apps publish their live "draggable from focused window" set so the keyboard
2821
+ * "Attach from app…" menu can offer them. Hosts call this each time the user-visible
2822
+ * draggable list changes; passing an empty array clears the entry for this `appId`.
2823
+ */
2824
+ publishDraggables(appId, items) {
2825
+ if (items.length === 0) {
2826
+ const existing = this._draggablesByApp.get(appId);
2827
+ if (existing)
2828
+ existing.set([]);
2829
+ return;
2830
+ }
2831
+ this.bucketFor(appId).set(items);
2832
+ }
2833
+ /**
2834
+ * Reactive read of the draggable set published for `appId`. Empty when none. The
2835
+ * returned signal is stable across calls (cached by `appId`), so consumers can use
2836
+ * it as a stable input to `computed()`.
2837
+ */
2838
+ draggablesFor(appId) {
2839
+ return this.bucketFor(appId).asReadonly();
2840
+ }
2841
+ /** Lazy-init the per-appId writable bucket. Returns the writable handle for internal use. */
2842
+ bucketFor(appId) {
2843
+ let bucket = this._draggablesByApp.get(appId);
2844
+ if (!bucket) {
2845
+ bucket = signal([]);
2846
+ this._draggablesByApp.set(appId, bucket);
2847
+ }
2848
+ return bucket;
2849
+ }
2850
+ _generation = 0;
2851
+ _owners = new Map();
2852
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AgentDropRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2853
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AgentDropRegistry, providedIn: 'root' });
2854
+ }
2855
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AgentDropRegistry, decorators: [{
2856
+ type: Injectable,
2857
+ args: [{ providedIn: 'root' }]
2858
+ }] });
2859
+ function compositeKey(kind, appId) {
2860
+ return `${kind}::${appId}`;
2861
+ }
2862
+
2863
+ /**
2864
+ * Agent input contracts.
2865
+ *
2866
+ * These types define the wire and DI surface for the new `<fly-agent-input>` orchestrator
2867
+ * (Phase 2+). Phase 1 ships only the contracts: payload envelope, command/drop registry
2868
+ * shapes, and chip-host inputs. The concrete components consume them later.
2869
+ *
2870
+ * Design rules:
2871
+ * - All fields are `readonly` — the registries store these in signal stores and we never
2872
+ * want a host mutating after registration. Hosts construct fresh objects to update.
2873
+ * - The discriminated kinds (`scope`, `mode`, `kind`) carry the entire variance — no
2874
+ * boolean flags. New variants extend the union without breaking existing consumers.
2875
+ * - The `Type<AgentChipHostInputs<T>>` shape is intentional: chip components declare
2876
+ * `payload`, `mode`, optional `onRemove` as Angular `input()` signals; the registry
2877
+ * wires them via `NgComponentOutlet` inputs in the consumer. The component reference
2878
+ * itself never leaks across the federation boundary by value — only by class identity.
2879
+ */
2880
+ /** Frozen MIME used by `flyAgentDraggable` and the drop-zone reader. Never change without a DS major. */
2881
+ const AGENT_DRAG_MIME = 'application/x-fly-agent-payload+json';
2882
+ /** Frozen payload envelope version. Bump only with a published DS major. */
2883
+ const AGENT_PAYLOAD_VERSION = 1;
2884
+ const DEFAULT_AGENT_PAYLOAD_LIMITS = Object.freeze({
2885
+ maxJsonBytes: 32 * 1024,
2886
+ maxPlainTextFallbackBytes: 8 * 1024,
2887
+ maxStringFieldBytes: 4 * 1024,
2888
+ });
2889
+
2890
+ /**
2891
+ * Pure utility helpers for agent drag payload size guarding.
2892
+ *
2893
+ * Used by `flyAgentDraggable` at `dragstart` (Phase 2) and by app pre-flight code that
2894
+ * wants a clean payload before constructing the directive input. No Angular DI, no
2895
+ * runtime side effects — safe to import from anywhere.
2896
+ *
2897
+ * Performance notes:
2898
+ * - The `TextEncoder` is lazily allocated and cached. The constructor runs once per
2899
+ * module instance even under federation (singleton DS).
2900
+ * - String trimming uses `Intl.Segmenter` when available (all evergreen browsers
2901
+ * including Safari 14.1+) so we cut on grapheme boundaries — emoji ZWJ sequences,
2902
+ * combining marks, regional indicator pairs all stay intact. Fallback is
2903
+ * UTF-16 code-unit slice, which is a graceful degradation: it can split a
2904
+ * surrogate pair, but the resulting JSON is still syntactically valid and
2905
+ * downstream renderers handle the replacement char.
2906
+ */
2907
+ /**
2908
+ * Thrown by {@link trimAgentPayload} when even a fully string-trimmed envelope still
2909
+ * exceeds `maxJsonBytes` (e.g. an envelope with thousands of nested empty objects /
2910
+ * array entries / property keys). Subclasses `RangeError` so callers using the standard
2911
+ * `instanceof RangeError` check still match, while exposing the structured fields
2912
+ * (`actualBytes`, `limitBytes`, `reason: 'json_too_large'`) needed for telemetry and
2913
+ * symmetric handling with {@link AgentPayloadValidationResult} failures.
2914
+ */
2915
+ class AgentPayloadOversizeError extends RangeError {
2916
+ actualBytes;
2917
+ limitBytes;
2918
+ reason = 'json_too_large';
2919
+ constructor(message, actualBytes, limitBytes) {
2920
+ super(message);
2921
+ this.actualBytes = actualBytes;
2922
+ this.limitBytes = limitBytes;
2923
+ this.name = 'AgentPayloadOversizeError';
2924
+ }
2925
+ }
2926
+ /** Cap on the dotted `fieldPath` string so a 10 MB property-key payload can't lock dev tools when `console.warn`d. */
2927
+ const FIELD_PATH_MAX_CHARS = 256;
2928
+ /** Append a path segment, capping the result at {@link FIELD_PATH_MAX_CHARS} chars (with `…` marker). */
2929
+ function appendPath(base, segment) {
2930
+ const next = base + segment;
2931
+ if (next.length <= FIELD_PATH_MAX_CHARS)
2932
+ return next;
2933
+ return next.slice(0, FIELD_PATH_MAX_CHARS - 1) + '…';
2934
+ }
2935
+ let _cachedEncoder = null;
2936
+ function getEncoder() {
2937
+ return (_cachedEncoder ??= new TextEncoder());
2938
+ }
2939
+ /** UTF-8 byte length of `s`. Cheap; cached encoder. */
2940
+ function utf8ByteLength(s) {
2941
+ return getEncoder().encode(s).length;
2942
+ }
2943
+ /**
2944
+ * Trim `input` so the UTF-8 byte length of the result is `<= maxBytes`. When trimming
2945
+ * occurs, append `ellipsis` (default `…`, U+2026) — its bytes are included in the cap.
2946
+ *
2947
+ * Idempotent: `trimAgentString(trimAgentString(s, n), n)` equals `trimAgentString(s, n)`.
2948
+ *
2949
+ * Edge cases:
2950
+ * - `maxBytes` shorter than the ellipsis itself returns an empty string.
2951
+ * - `Intl.Segmenter` (when present) ensures we never split a grapheme cluster.
2952
+ * - When the encoded length is already within the cap, returns `input` unchanged.
2953
+ */
2954
+ function trimAgentString(input, maxBytes, ellipsis = '…') {
2955
+ if (maxBytes <= 0)
2956
+ return '';
2957
+ const enc = getEncoder();
2958
+ const inputLen = enc.encode(input).length;
2959
+ if (inputLen <= maxBytes)
2960
+ return input;
2961
+ const ellipsisLen = enc.encode(ellipsis).length;
2962
+ if (maxBytes < ellipsisLen)
2963
+ return '';
2964
+ const budget = maxBytes - ellipsisLen;
2965
+ // Segmenter path: walk graphemes, accumulate until next would overflow.
2966
+ const segmenterCtor = typeof Intl !== 'undefined' && Intl.Segmenter;
2967
+ if (segmenterCtor) {
2968
+ const segmenter = new segmenterCtor(undefined, { granularity: 'grapheme' });
2969
+ let used = 0;
2970
+ let acc = '';
2971
+ for (const seg of segmenter.segment(input)) {
2972
+ const segLen = enc.encode(seg.segment).length;
2973
+ if (used + segLen > budget)
2974
+ break;
2975
+ acc += seg.segment;
2976
+ used += segLen;
2977
+ }
2978
+ return acc + ellipsis;
2979
+ }
2980
+ // Fallback: code-unit slice; verify byte length and shrink until under budget.
2981
+ let cut = input.length;
2982
+ while (cut > 0 && enc.encode(input.slice(0, cut)).length > budget) {
2983
+ cut--;
2984
+ }
2985
+ return input.slice(0, cut) + ellipsis;
2986
+ }
2987
+ /**
2988
+ * Validate an {@link AgentDragPayload} against the supplied limits (or
2989
+ * {@link DEFAULT_AGENT_PAYLOAD_LIMITS} when omitted). Returns `{ ok: true }` or a
2990
+ * structured failure with the offending field path and byte counts.
2991
+ *
2992
+ * Order of checks (cheap → expensive):
2993
+ * 1. `version` matches the frozen {@link AGENT_PAYLOAD_VERSION}.
2994
+ * 2. `kind` is a non-empty string.
2995
+ * 3. `plainTextFallback` fits its cap.
2996
+ * 4. Every string in `payload` (recursively) fits the per-field cap.
2997
+ * 5. The full `JSON.stringify(envelope)` fits the JSON cap.
2998
+ *
2999
+ * The walker stops on the first oversize string field — the intent is to surface
3000
+ * actionable feedback, not enumerate every offender.
3001
+ */
3002
+ function validateAgentPayload(payload, limits) {
3003
+ const eff = { ...DEFAULT_AGENT_PAYLOAD_LIMITS, ...(limits ?? {}) };
3004
+ if (payload.version !== AGENT_PAYLOAD_VERSION) {
3005
+ return { ok: false, reason: 'invalid_version' };
3006
+ }
3007
+ if (typeof payload.kind !== 'string' || payload.kind.length === 0) {
3008
+ return { ok: false, reason: 'invalid_kind' };
3009
+ }
3010
+ const fbBytes = utf8ByteLength(payload.plainTextFallback ?? '');
3011
+ if (fbBytes > eff.maxPlainTextFallbackBytes) {
3012
+ return {
3013
+ ok: false,
3014
+ reason: 'fallback_too_large',
3015
+ fieldPath: 'plainTextFallback',
3016
+ actualBytes: fbBytes,
3017
+ limitBytes: eff.maxPlainTextFallbackBytes,
3018
+ };
3019
+ }
3020
+ const fieldFailure = walkStringsForOverage(payload.payload, 'payload', eff.maxStringFieldBytes);
3021
+ if (fieldFailure)
3022
+ return fieldFailure;
3023
+ const json = safeStringify(payload);
3024
+ const jsonBytes = utf8ByteLength(json);
3025
+ if (jsonBytes > eff.maxJsonBytes) {
3026
+ return {
3027
+ ok: false,
3028
+ reason: 'json_too_large',
3029
+ actualBytes: jsonBytes,
3030
+ limitBytes: eff.maxJsonBytes,
3031
+ };
3032
+ }
3033
+ return { ok: true };
3034
+ }
3035
+ /**
3036
+ * Returns a NEW payload with all string fields trimmed to fit the per-field cap, the
3037
+ * `plainTextFallback` trimmed to its cap, and the full envelope re-checked against the
3038
+ * JSON cap. Idempotent — a second call against the trimmed result yields equal output.
3039
+ *
3040
+ * Throws {@link AgentPayloadOversizeError} only if even an empty trim couldn't shrink it
3041
+ * under the cap (e.g. an envelope with 1000+ nested empty objects). The error subclasses
3042
+ * `RangeError`, so legacy `instanceof RangeError` checks still match. Apps that hit this
3043
+ * should re-shape `payload` rather than call again.
3044
+ */
3045
+ function trimAgentPayload(payload, limits) {
3046
+ const eff = { ...DEFAULT_AGENT_PAYLOAD_LIMITS, ...(limits ?? {}) };
3047
+ const trimmedFallback = trimAgentString(payload.plainTextFallback ?? '', eff.maxPlainTextFallbackBytes);
3048
+ const trimmedPayload = mapStrings(payload.payload, (s) => trimAgentString(s, eff.maxStringFieldBytes));
3049
+ const next = {
3050
+ ...payload,
3051
+ plainTextFallback: trimmedFallback,
3052
+ payload: trimmedPayload,
3053
+ };
3054
+ const jsonBytes = utf8ByteLength(safeStringify(next));
3055
+ if (jsonBytes > eff.maxJsonBytes) {
3056
+ throw new AgentPayloadOversizeError(`AgentDragPayload exceeds maxJsonBytes (${jsonBytes} > ${eff.maxJsonBytes}) after string trimming. Re-shape the payload.`, jsonBytes, eff.maxJsonBytes);
3057
+ }
3058
+ return next;
3059
+ }
3060
+ // ─── Internal helpers ───────────────────────────────────────────────────────
3061
+ /**
3062
+ * Recursively walks `value`, returning the first string field that exceeds `maxBytes`
3063
+ * (as a structured failure). Returns `null` when every string fits.
3064
+ *
3065
+ * Path uses dotted property-access notation: `payload.body`, `payload.tags[2]`. Cycles
3066
+ * are detected via a WeakSet so attacker-crafted self-references don't loop forever.
3067
+ * Path strings are capped at {@link FIELD_PATH_MAX_CHARS} characters via {@link appendPath}
3068
+ * to prevent a malicious payload with multi-MB property keys from producing a multi-MB
3069
+ * `fieldPath` that locks up dev tools when logged.
3070
+ */
3071
+ function walkStringsForOverage(value, path, maxBytes, seen = new WeakSet()) {
3072
+ if (typeof value === 'string') {
3073
+ const bytes = utf8ByteLength(value);
3074
+ if (bytes > maxBytes) {
3075
+ return {
3076
+ ok: false,
3077
+ reason: 'field_too_large',
3078
+ fieldPath: path,
3079
+ actualBytes: bytes,
3080
+ limitBytes: maxBytes,
3081
+ };
3082
+ }
3083
+ return null;
3084
+ }
3085
+ if (value === null || typeof value !== 'object')
3086
+ return null;
3087
+ if (seen.has(value))
3088
+ return null;
3089
+ seen.add(value);
3090
+ if (Array.isArray(value)) {
3091
+ for (let i = 0; i < value.length; i++) {
3092
+ const r = walkStringsForOverage(value[i], appendPath(path, `[${i}]`), maxBytes, seen);
3093
+ if (r)
3094
+ return r;
3095
+ }
3096
+ return null;
3097
+ }
3098
+ for (const [k, v] of Object.entries(value)) {
3099
+ const r = walkStringsForOverage(v, appendPath(path, `.${k}`), maxBytes, seen);
3100
+ if (r)
3101
+ return r;
3102
+ }
3103
+ return null;
3104
+ }
3105
+ /**
3106
+ * Recursively maps every string leaf via `fn`, preserving object/array structure.
3107
+ * Cycles are short-circuited to `null` rather than throwing — the caller already
3108
+ * has a JSON.stringify cap that will catch any pathological input.
3109
+ */
3110
+ function mapStrings(value, fn, seen = new WeakSet()) {
3111
+ if (typeof value === 'string')
3112
+ return fn(value);
3113
+ if (value === null || typeof value !== 'object')
3114
+ return value;
3115
+ if (seen.has(value))
3116
+ return null;
3117
+ seen.add(value);
3118
+ if (Array.isArray(value)) {
3119
+ return value.map((v) => mapStrings(v, fn, seen));
3120
+ }
3121
+ const out = {};
3122
+ for (const [k, v] of Object.entries(value)) {
3123
+ out[k] = mapStrings(v, fn, seen);
3124
+ }
3125
+ return out;
3126
+ }
3127
+ /** `JSON.stringify` with a cycle-safe replacer — defends against attacker-crafted graphs. */
3128
+ function safeStringify(value) {
3129
+ const seen = new WeakSet();
3130
+ return JSON.stringify(value, (_k, v) => {
3131
+ if (v !== null && typeof v === 'object') {
3132
+ if (seen.has(v))
3133
+ return '[Circular]';
3134
+ seen.add(v);
3135
+ }
3136
+ return v;
3137
+ });
3138
+ }
3139
+
3140
+ /**
3141
+ * Strict whitelist for the ghost element id. Letter-led ASCII, allows word/hyphen/colon/dot,
3142
+ * capped at 128 chars. Anything else is rejected before reaching `getElementById` so a
3143
+ * future maintainer can't swap to `querySelector` and quietly reintroduce a CSS-injection
3144
+ * vector. (`getElementById` itself is string-equality-safe; the whitelist is defence-in-depth.)
3145
+ */
3146
+ const GHOST_ID_PATTERN = /^[A-Za-z][\w\-:.]{0,127}$/;
3147
+ /**
3148
+ * Single canonical way to declare a drag source for the agent input.
3149
+ *
3150
+ * Apply on any element a remote wants to make draggable into the `<fly-agent-input>`:
3151
+ *
3152
+ * ```html
3153
+ * <article [flyAgentDraggable]="() => buildPayload(trend)" flyAgentDraggableGhostId="trend-ghost">…</article>
3154
+ * ```
3155
+ *
3156
+ * On `dragstart` the directive:
3157
+ * 1. Resolves the payload (calls the builder fn if supplied; otherwise reads the
3158
+ * static value).
3159
+ * 2. Validates against {@link DEFAULT_AGENT_PAYLOAD_LIMITS} via
3160
+ * {@link validateAgentPayload}. On failure it `console.warn`s with the structured
3161
+ * reason and writes ONLY the `text/plain` fallback — the receiver still sees a
3162
+ * legible drop, but the typed envelope is skipped so a misconfigured app can't
3163
+ * corrupt downstream consumers with an oversize payload.
3164
+ * 3. Writes the typed MIME (`application/x-fly-agent-payload+json`) plus the
3165
+ * `text/plain` fallback so non-Fly drop targets still get useful text.
3166
+ * 4. Sets `effectAllowed = 'copy'` (we never *move* domain objects across windows).
3167
+ * 5. When `flyAgentDraggableGhostId` resolves to an element in the DOM, calls
3168
+ * `setDragImage` to use that element as the drag preview; otherwise the browser
3169
+ * default applies.
3170
+ *
3171
+ * Notes:
3172
+ * - Builder form is preferred over static — it captures fresh state at `dragstart`,
3173
+ * not at directive init. A static `AgentDragPayload` is fine for items whose
3174
+ * domain object never changes after the row mounts.
3175
+ * - The directive sets `[attr.draggable]="true"` so callers don't have to.
3176
+ */
3177
+ class FlyAgentDraggableDirective {
3178
+ /**
3179
+ * Either a static payload or a builder evaluated at `dragstart` (preferred — captures
3180
+ * fresh state). `input.required` so misuse fails at template compile time.
3181
+ */
3182
+ flyAgentDraggable = input.required(...(ngDevMode ? [{ debugName: "flyAgentDraggable" }] : /* istanbul ignore next */ []));
3183
+ /**
3184
+ * Optional ghost element id (an existing element in the DOM whose snapshot becomes
3185
+ * the drag image). Falls back to the default browser drag image when absent or the
3186
+ * element can't be resolved.
3187
+ *
3188
+ * Must be a valid HTML id (ASCII, letter-led). `getElementById` is intentional —
3189
+ * do NOT swap to `querySelector` without re-introducing this whitelist.
3190
+ */
3191
+ flyAgentDraggableGhostId = input(undefined, ...(ngDevMode ? [{ debugName: "flyAgentDraggableGhostId" }] : /* istanbul ignore next */ []));
3192
+ doc = inject(DOCUMENT$1);
3193
+ onDragStart(ev) {
3194
+ const dt = ev.dataTransfer;
3195
+ if (!dt)
3196
+ return;
3197
+ const payload = this.resolvePayload();
3198
+ const validation = validateAgentPayload(payload);
3199
+ if (!validation.ok) {
3200
+ // eslint-disable-next-line no-console
3201
+ console.warn('[flyAgentDraggable] payload validation failed; degrading to text/plain', validation);
3202
+ dt.setData('text/plain', payload.plainTextFallback ?? '');
3203
+ dt.effectAllowed = 'copy';
3204
+ return;
3205
+ }
3206
+ dt.setData(AGENT_DRAG_MIME, JSON.stringify(payload));
3207
+ dt.setData('text/plain', payload.plainTextFallback ?? '');
3208
+ dt.effectAllowed = 'copy';
3209
+ const ghostId = this.flyAgentDraggableGhostId();
3210
+ if (ghostId) {
3211
+ if (!GHOST_ID_PATTERN.test(ghostId)) {
3212
+ // Don't echo the rejected id (caller-controlled, could be huge).
3213
+ // eslint-disable-next-line no-console
3214
+ console.warn('[flyAgentDraggable] ignoring ghost id', { reason: 'invalid_ghost_id' });
3215
+ }
3216
+ else {
3217
+ const ghost = this.doc.getElementById(ghostId);
3218
+ if (ghost) {
3219
+ // Center the drag image; ResizeObserver-aware sizing is left to the host element.
3220
+ dt.setDragImage(ghost, ghost.clientWidth / 2, ghost.clientHeight / 2);
3221
+ }
3222
+ }
3223
+ }
3224
+ }
3225
+ resolvePayload() {
3226
+ const raw = this.flyAgentDraggable();
3227
+ return typeof raw === 'function' ? raw() : raw;
3228
+ }
3229
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyAgentDraggableDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
3230
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.5", type: FlyAgentDraggableDirective, isStandalone: true, selector: "[flyAgentDraggable]", inputs: { flyAgentDraggable: { classPropertyName: "flyAgentDraggable", publicName: "flyAgentDraggable", isSignal: true, isRequired: true, transformFunction: null }, flyAgentDraggableGhostId: { classPropertyName: "flyAgentDraggableGhostId", publicName: "flyAgentDraggableGhostId", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "dragstart": "onDragStart($event)" }, properties: { "attr.draggable": "\"true\"" } }, ngImport: i0 });
3231
+ }
3232
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: FlyAgentDraggableDirective, decorators: [{
3233
+ type: Directive,
3234
+ args: [{
3235
+ selector: '[flyAgentDraggable]',
3236
+ standalone: true,
3237
+ host: {
3238
+ '[attr.draggable]': '"true"',
3239
+ },
3240
+ }]
3241
+ }], propDecorators: { flyAgentDraggable: [{ type: i0.Input, args: [{ isSignal: true, alias: "flyAgentDraggable", required: true }] }], flyAgentDraggableGhostId: [{ type: i0.Input, args: [{ isSignal: true, alias: "flyAgentDraggableGhostId", required: false }] }], onDragStart: [{
3242
+ type: HostListener,
3243
+ args: ['dragstart', ['$event']]
3244
+ }] } });
3245
+
3246
+ /**
3247
+ * Verbatim TypeScript port of the C# `LanguageDTO[] _AllLanguages` array supplied by
3248
+ * product. **31 entries**, in the canonical order. Three RTL flagged: `he-IL`, `ur-IN`,
3249
+ * `ar-AE`. Includes `cmn-Hant-TW` for Chinese and `ka-GE` for Georgian.
3250
+ *
3251
+ * The order matches the C# source byte-for-byte — do not re-sort. Downstream tests
3252
+ * (`fly-locale-catalog.spec.ts`) pin the count, the unique-key constraints, and the
3253
+ * RTL set; if you reorder or add an entry, those tests will guide the fix.
3254
+ *
3255
+ * The strings carry Cyrillic, Hebrew, Vietnamese, Thai, Indonesian, Bengali, Devanagari,
3256
+ * Urdu, Catalan, Polish, Korean, Russian, Japanese, Han, Turkish, Romanian, Norwegian,
3257
+ * Hungarian, Greek, Finnish, Danish, Dutch, Swedish, Portuguese, Italian, German,
3258
+ * Spanish, French, English, Arabic, and Georgian. The file is saved as UTF-8 without
3259
+ * BOM (matches every other locale file in the repo). The `…` (U+2026) ellipsis is
3260
+ * preserved verbatim.
3261
+ */
3262
+ const FLY_LOCALE_CATALOG = Object.freeze([
3263
+ {
3264
+ prefix: 'UK',
3265
+ dialect: 'uk-UA',
3266
+ englishName: 'Ukrainian',
3267
+ arabicName: 'الأوكرانية',
3268
+ nativeName: 'Українська',
3269
+ detected: 'Виявлена мова',
3270
+ searching: 'Шукаючи',
3271
+ elevating: 'Підняття до',
3272
+ translating: 'Переклад на',
3273
+ generatingAnswersForYou: 'Створення відповідей для вас...',
3274
+ learnMore: 'Дізнайтеся більше',
3275
+ waitingForResponseMsg: 'Будь ласка, наберіться терпіння, ми ще думаємо',
3276
+ isRtl: false,
3277
+ },
3278
+ {
3279
+ prefix: 'HE',
3280
+ dialect: 'he-IL',
3281
+ englishName: 'Hebrew',
3282
+ arabicName: 'العبرية',
3283
+ nativeName: 'עברית',
3284
+ detected: 'שפה מזוהה',
3285
+ searching: 'מחפש אחר',
3286
+ elevating: 'מרומם ל',
3287
+ translating: 'תרגום ל',
3288
+ generatingAnswersForYou: 'מייצר עבורך תשובות...',
3289
+ learnMore: 'למידע נוסף',
3290
+ waitingForResponseMsg: 'אנא התאזרו בסבלנות, אנחנו עדיין חושבים',
3291
+ isRtl: true,
3292
+ },
3293
+ {
3294
+ prefix: 'VI',
3295
+ dialect: 'vi-VN',
3296
+ englishName: 'Vietnamese',
3297
+ arabicName: 'الفيتنامية',
3298
+ nativeName: 'Tiếng Việt',
3299
+ detected: 'Ngôn ngữ được phát hiện',
3300
+ searching: 'Tìm kiếm',
3301
+ elevating: 'Nâng lên',
3302
+ translating: 'Dịch sang',
3303
+ generatingAnswersForYou: 'Đang tạo câu trả lời cho bạn...',
3304
+ learnMore: 'Tìm hiểu thêm',
3305
+ waitingForResponseMsg: 'Vui lòng kiên nhẫn, chúng tôi vẫn đang suy nghĩ',
3306
+ isRtl: false,
3307
+ },
3308
+ {
3309
+ prefix: 'TH',
3310
+ dialect: 'th-TH',
3311
+ englishName: 'Thai',
3312
+ arabicName: 'التايلاندية',
3313
+ nativeName: 'ไทย',
3314
+ detected: 'ภาษาที่ตรวจพบ',
3315
+ searching: 'ค้นหา',
3316
+ elevating: 'ยกระดับเป็น',
3317
+ translating: 'การแปล',
3318
+ generatingAnswersForYou: 'กำลังสร้างคำตอบให้กับคุณ...',
3319
+ learnMore: 'เรียนรู้เพิ่มเติม',
3320
+ waitingForResponseMsg: 'โปรดอดใจรอ, พวกเรายังคงกำลังคิดอยู่',
3321
+ isRtl: false,
3322
+ },
3323
+ {
3324
+ prefix: 'ID',
3325
+ dialect: 'id-ID',
3326
+ englishName: 'Indonesian',
3327
+ arabicName: 'الإندونيسية',
3328
+ nativeName: 'Bahasa Indonesia',
3329
+ detected: 'Bahasa yang terdeteksi',
3330
+ searching: 'Mencari',
3331
+ elevating: 'Meninggalkan ke',
3332
+ translating: 'Menerjemahkan ke',
3333
+ generatingAnswersForYou: 'Menghasilkan jawaban untuk Anda...',
3334
+ learnMore: 'Cari tahu lebih lanjut',
3335
+ waitingForResponseMsg: 'Harap bersabar, kami masih sedang berpikir',
3336
+ isRtl: false,
3337
+ },
3338
+ {
3339
+ prefix: 'BN',
3340
+ dialect: 'bn-BD',
3341
+ englishName: 'Bengali',
3342
+ arabicName: 'البنغالية',
3343
+ nativeName: 'বাংলা',
3344
+ detected: 'সনাক্ত করা ভাষা',
3345
+ searching: 'খুঁজছি',
3346
+ elevating: 'উন্নত',
3347
+ translating: 'অনুবাদ',
3348
+ generatingAnswersForYou: 'আপনার জন্য উত্তর তৈরি করা হচ্ছে...',
3349
+ learnMore: 'আরো জানুন',
3350
+ waitingForResponseMsg: 'অনুগ্রহ করে ধৈর্য ধরুন, আমরা এখনও চিন্তা করছি',
3351
+ isRtl: false,
3352
+ },
3353
+ {
3354
+ prefix: 'HI',
3355
+ dialect: 'hi-IN',
3356
+ englishName: 'Hindi',
3357
+ arabicName: 'الهندية',
3358
+ nativeName: 'हिन्दी',
3359
+ detected: 'पता चला भाषा',
3360
+ searching: 'के लिए खोज रहे हैं',
3361
+ elevating: 'के लिए ऊंचा',
3362
+ translating: 'अनुवाद करना',
3363
+ generatingAnswersForYou: 'आपके लिए उत्तर तैयार किया जा रहा है...',
3364
+ learnMore: 'और जानें',
3365
+ waitingForResponseMsg: 'कृपया धैर्य रखें, हम अभी सोच रहे हैं',
3366
+ isRtl: false,
3367
+ },
3368
+ {
3369
+ prefix: 'UR',
3370
+ dialect: 'ur-IN',
3371
+ englishName: 'Urdu',
3372
+ arabicName: 'الأردية',
3373
+ nativeName: 'اردو',
3374
+ detected: 'پتہ چلا زبان',
3375
+ searching: 'کے لیے تلاش',
3376
+ elevating: 'بلند کرنا',
3377
+ translating: 'ترجمہ کرنا',
3378
+ generatingAnswersForYou: 'آپ کے لیے جوابات تیار کیے جا رہے ہیں...',
3379
+ learnMore: 'مزید جانیں',
3380
+ waitingForResponseMsg: 'براہ کرم صبر کریں، ہم ابھی سوچ رہے ہیں۔',
3381
+ isRtl: true,
3382
+ },
3383
+ {
3384
+ prefix: 'CA',
3385
+ dialect: 'ca-ES',
3386
+ englishName: 'Catalan',
3387
+ arabicName: 'الكتالانية',
3388
+ nativeName: 'Català',
3389
+ detected: 'Idioma detectat',
3390
+ searching: 'Buscant',
3391
+ elevating: 'Elevant a',
3392
+ translating: 'Traduir a',
3393
+ generatingAnswersForYou: 'Generant respostes per a tu...',
3394
+ learnMore: 'Aprèn més',
3395
+ waitingForResponseMsg: 'Si us plau, tingueu paciència, encara estem pensant',
3396
+ isRtl: false,
3397
+ },
3398
+ {
3399
+ prefix: 'PL',
3400
+ dialect: 'pl-PL',
3401
+ englishName: 'Polish',
3402
+ arabicName: 'البولندية',
3403
+ nativeName: 'Polski',
3404
+ detected: 'Wykryty język',
3405
+ searching: 'Szukać',
3406
+ elevating: 'Podniesienie do',
3407
+ translating: 'Tłumaczenie na',
3408
+ generatingAnswersForYou: 'Generowanie odpowiedzi dla Ciebie...',
3409
+ learnMore: 'Dowiedz się więcej',
3410
+ waitingForResponseMsg: 'Proszę o cierpliwość, nadal myślimy',
3411
+ isRtl: false,
3412
+ },
3413
+ {
3414
+ prefix: 'KO',
3415
+ dialect: 'ko-KR',
3416
+ englishName: 'Korean',
3417
+ arabicName: 'الكورية',
3418
+ nativeName: '한국어',
3419
+ detected: '탐지 언어',
3420
+ searching: '검색',
3421
+ elevating: '상승',
3422
+ translating: '번역',
3423
+ generatingAnswersForYou: '답변을 생성하는 중...',
3424
+ learnMore: '더 알아보기',
3425
+ waitingForResponseMsg: '잠시만 기다려 주세요, 저희는 아직 생각 중입니다',
3426
+ isRtl: false,
3427
+ },
3428
+ {
3429
+ prefix: 'RU',
3430
+ dialect: 'ru-RU',
3431
+ englishName: 'Russian',
3432
+ arabicName: 'الروسية',
3433
+ nativeName: 'Русский',
3434
+ detected: 'Обнаруженный язык',
3435
+ searching: 'В поисках',
3436
+ elevating: 'Поднимаясь до',
3437
+ translating: 'Перевод',
3438
+ generatingAnswersForYou: 'Генерация ответов для вас...',
3439
+ learnMore: 'Узнать больше',
3440
+ waitingForResponseMsg: 'Пожалуйста, подождите, мы всё ещё думаем',
3441
+ isRtl: false,
3442
+ },
3443
+ {
3444
+ prefix: 'JA',
3445
+ dialect: 'ja-JP',
3446
+ englishName: 'Japanese',
3447
+ arabicName: 'اليابانية',
3448
+ nativeName: '日本語',
3449
+ detected: '検出言語',
3450
+ searching: '探している',
3451
+ elevating: '昇格',
3452
+ translating: '翻訳',
3453
+ generatingAnswersForYou: 'あなたのための答えを生成中...',
3454
+ learnMore: 'もっと詳しく',
3455
+ waitingForResponseMsg: '少々お待ちください、ただいま考え中です',
3456
+ isRtl: false,
3457
+ },
3458
+ {
3459
+ prefix: 'ZH',
3460
+ dialect: 'cmn-Hant-TW',
3461
+ englishName: 'Chinese',
3462
+ arabicName: 'الصينية',
3463
+ nativeName: '中文',
3464
+ detected: '检测语言',
3465
+ searching: '搜索',
3466
+ elevating: '提升到',
3467
+ translating: '翻译成',
3468
+ generatingAnswersForYou: '为您生成答案...',
3469
+ learnMore: '了解更多',
3470
+ waitingForResponseMsg: '请耐心等待,我们还在思考',
3471
+ isRtl: false,
3472
+ },
3473
+ {
3474
+ prefix: 'TR',
3475
+ dialect: 'tr-TR',
3476
+ englishName: 'Turkish',
3477
+ arabicName: 'التركية',
3478
+ nativeName: 'Türkçe',
3479
+ detected: 'Tespit edilen dil',
3480
+ searching: 'Arama',
3481
+ elevating: 'Yükseltme',
3482
+ translating: 'Çevirme',
3483
+ generatingAnswersForYou: 'Sizin için yanıtlar üretiliyor...',
3484
+ learnMore: 'Daha fazla bilgi edin',
3485
+ waitingForResponseMsg: 'Lütfen sabırlı olun, hâlâ düşünüyoruz',
3486
+ isRtl: false,
3487
+ },
3488
+ {
3489
+ prefix: 'RO',
3490
+ dialect: 'ro-RO',
3491
+ englishName: 'Romanian',
3492
+ arabicName: 'الرومانية',
3493
+ nativeName: 'Română',
3494
+ detected: 'Limba detectată',
3495
+ searching: 'Căutare de',
3496
+ elevating: 'Elevator la',
3497
+ translating: 'Traducerea în',
3498
+ generatingAnswersForYou: 'Generarea de răspunsuri pentru tine...',
3499
+ learnMore: 'Află mai multe',
3500
+ waitingForResponseMsg: 'Vă rugăm să aveți răbdare, încă ne gândim',
3501
+ isRtl: false,
3502
+ },
3503
+ {
3504
+ prefix: 'NO',
3505
+ dialect: 'nb-NO',
3506
+ englishName: 'Norwegian',
3507
+ arabicName: 'النرويجية',
3508
+ nativeName: 'Norsk',
3509
+ detected: 'Oppdaget språk',
3510
+ searching: 'Leter etter',
3511
+ elevating: 'Løfter til',
3512
+ translating: 'Oversette til',
3513
+ generatingAnswersForYou: 'Genererer svar for deg...',
3514
+ learnMore: 'Lær mer',
3515
+ waitingForResponseMsg: 'Vennligst vær tålmodig, vi tenker fortsatt',
3516
+ isRtl: false,
3517
+ },
3518
+ {
3519
+ prefix: 'HU',
3520
+ dialect: 'hu-HU',
3521
+ englishName: 'Hungarian',
3522
+ arabicName: 'الهنغارية',
3523
+ nativeName: 'Magyar',
3524
+ detected: 'Detektált nyelv',
3525
+ searching: 'Keresés-ra,-re',
3526
+ elevating: 'Felemelve',
3527
+ translating: 'Fordítás',
3528
+ generatingAnswersForYou: 'Válaszok generálása az Ön számára...',
3529
+ learnMore: 'Tudj meg többet',
3530
+ waitingForResponseMsg: 'Kérjük, legyen türelemmel, még gondolkodunk',
3531
+ isRtl: false,
3532
+ },
3533
+ {
3534
+ prefix: 'EL',
3535
+ dialect: 'el-GR',
3536
+ englishName: 'Greek',
3537
+ arabicName: 'اليونانية',
3538
+ nativeName: 'Ελληνικά',
3539
+ detected: 'Εντοπισμένη γλώσσα',
3540
+ searching: 'Ψάχνοντας για',
3541
+ elevating: 'Ανυψώ',
3542
+ translating: 'Μεταφράζοντας σε',
3543
+ generatingAnswersForYou: 'Δημιουργία απαντήσεων για εσάς...',
3544
+ learnMore: 'Μάθε περισσότερα',
3545
+ waitingForResponseMsg: 'Παρακαλώ περιμένετε με υπομονή, σκεφτόμαστε ακόμα',
3546
+ isRtl: false,
3547
+ },
3548
+ {
3549
+ prefix: 'FI',
3550
+ dialect: 'fi-FI',
3551
+ englishName: 'Finnish',
3552
+ arabicName: 'الفنلندية',
3553
+ nativeName: 'Suomi',
3554
+ detected: 'Havaittu kieli',
3555
+ searching: 'Etsimässä',
3556
+ elevating: 'Nousee jhk',
3557
+ translating: 'Kääntäen',
3558
+ generatingAnswersForYou: 'Luodaan vastauksia sinulle...',
3559
+ learnMore: 'Lue lisää',
3560
+ waitingForResponseMsg: 'Ole hyvä ja ole kärsivällinen, ajattelemme yhä',
3561
+ isRtl: false,
3562
+ },
3563
+ {
3564
+ prefix: 'DA',
3565
+ dialect: 'da-DK',
3566
+ englishName: 'Danish',
3567
+ arabicName: 'الدانماركية',
3568
+ nativeName: 'Dansk',
3569
+ detected: 'Opdaget sprog',
3570
+ searching: 'Leder efter',
3571
+ elevating: 'Hæve til',
3572
+ translating: 'Oversættelse til',
3573
+ generatingAnswersForYou: 'Generer svar til dig...',
3574
+ learnMore: 'Lær mere',
3575
+ waitingForResponseMsg: 'Vent venligst tålmodigt, vi tænker stadig',
3576
+ isRtl: false,
3577
+ },
3578
+ {
3579
+ prefix: 'NL',
3580
+ dialect: 'nl-NL',
3581
+ englishName: 'Dutch',
3582
+ arabicName: 'الهولندية',
3583
+ nativeName: 'Nederlands',
3584
+ detected: 'Gedetecteerde taal',
3585
+ searching: 'Op zoek naar',
3586
+ elevating: 'Verheffen tot',
3587
+ translating: 'Vertalen naar',
3588
+ generatingAnswersForYou: 'Antwoorden voor u genereren...',
3589
+ learnMore: 'Lees meer',
3590
+ waitingForResponseMsg: 'Gelieve geduld te hebben, we zijn nog aan het nadenken',
3591
+ isRtl: false,
3592
+ },
3593
+ {
3594
+ prefix: 'SV',
3595
+ dialect: 'sv-SE',
3596
+ englishName: 'Swedish',
3597
+ arabicName: 'السويدية',
3598
+ nativeName: 'Svenska',
3599
+ detected: 'Upptäckt språk',
3600
+ searching: 'Söker efter',
3601
+ elevating: 'Upphöjande till',
3602
+ translating: 'Översätta till',
3603
+ generatingAnswersForYou: 'Genererar svar åt dig...',
3604
+ learnMore: 'Läs mer',
3605
+ waitingForResponseMsg: 'Vänligen ha tålamod, vi tänker fortfarande',
3606
+ isRtl: false,
3607
+ },
3608
+ {
3609
+ prefix: 'PT',
3610
+ dialect: 'pt-PT',
3611
+ englishName: 'Portuguese',
3612
+ arabicName: 'البرتغالية',
3613
+ nativeName: 'Português',
3614
+ detected: 'Idioma detectado',
3615
+ searching: 'Procurando por',
3616
+ elevating: 'Elevando para',
3617
+ translating: 'Traduzindo para',
3618
+ generatingAnswersForYou: 'Gerando respostas para você...',
3619
+ learnMore: 'Saiba mais',
3620
+ waitingForResponseMsg: 'Por favor, tenha paciência, ainda estamos a pensar',
3621
+ isRtl: false,
3622
+ },
3623
+ {
3624
+ prefix: 'IT',
3625
+ dialect: 'it-IT',
3626
+ englishName: 'Italian',
3627
+ arabicName: 'الإيطالية',
3628
+ nativeName: 'Italiano',
3629
+ detected: 'Lingua rilevata',
3630
+ searching: 'Alla ricerca di',
3631
+ elevating: 'Elevare a',
3632
+ translating: 'Tradurre in',
3633
+ generatingAnswersForYou: 'Generazione di risposte per te...',
3634
+ learnMore: 'Scopri di più',
3635
+ waitingForResponseMsg: 'Per favore, abbi pazienza, stiamo ancora pensando',
3636
+ isRtl: false,
3637
+ },
3638
+ {
3639
+ prefix: 'DE',
3640
+ dialect: 'de-DE',
3641
+ englishName: 'German',
3642
+ arabicName: 'الألمانية',
3643
+ nativeName: 'Deutsch',
3644
+ detected: 'Erkannte Sprache',
3645
+ searching: 'Auf der Suche nach',
3646
+ elevating: 'Erhöhung zu',
3647
+ translating: 'Übersetzung auf',
3648
+ generatingAnswersForYou: 'Wir generieren Antworten für Sie...',
3649
+ learnMore: 'Erfahre mehr',
3650
+ waitingForResponseMsg: 'Bitte haben Sie Geduld, wir denken noch nach',
3651
+ isRtl: false,
3652
+ },
3653
+ {
3654
+ prefix: 'ES',
3655
+ dialect: 'es-AR',
3656
+ englishName: 'Spanish',
3657
+ arabicName: 'الأسبانية',
3658
+ nativeName: 'Español',
3659
+ detected: 'Idioma detectado',
3660
+ searching: 'Buscando',
3661
+ elevating: 'Elevando a',
3662
+ translating: 'Traduciendo a',
3663
+ generatingAnswersForYou: 'Generando respuestas para ti...',
3664
+ learnMore: 'Aprende más',
3665
+ waitingForResponseMsg: 'Por favor, ten paciencia, todavía estamos pensando',
3666
+ isRtl: false,
3667
+ },
3668
+ {
3669
+ prefix: 'FR',
3670
+ dialect: 'fr-FR',
3671
+ englishName: 'French',
3672
+ arabicName: 'الفرنسية',
3673
+ nativeName: 'Français',
3674
+ detected: 'Langue détectée',
3675
+ searching: 'À la recherche de',
3676
+ elevating: 'Élever à',
3677
+ translating: 'Traduire par',
3678
+ generatingAnswersForYou: 'Générer des réponses pour vous...',
3679
+ learnMore: 'En savoir plus',
3680
+ waitingForResponseMsg: 'Veuillez patienter, nous réfléchissons encore',
3681
+ isRtl: false,
3682
+ },
3683
+ {
3684
+ prefix: 'EN',
3685
+ dialect: 'en-US',
3686
+ englishName: 'English',
3687
+ arabicName: 'الإنجليزية',
3688
+ nativeName: 'English',
3689
+ detected: 'Detected language',
3690
+ searching: 'Searching for',
3691
+ elevating: 'Elevating to',
3692
+ translating: 'Translating to',
3693
+ generatingAnswersForYou: 'Generating answers for you...',
3694
+ learnMore: 'Learn more',
3695
+ waitingForResponseMsg: 'Please bear with us, we are still thinking',
3696
+ isRtl: false,
3697
+ },
3698
+ {
3699
+ prefix: 'AR',
3700
+ dialect: 'ar-AE',
3701
+ englishName: 'Arabic',
3702
+ arabicName: 'العربية',
3703
+ nativeName: 'العربية',
3704
+ detected: 'اللغة المكتشفة',
3705
+ searching: 'البحث عن',
3706
+ elevating: 'رفع إلى',
3707
+ translating: 'ترجمة إلى',
3708
+ generatingAnswersForYou: 'جارٍ إنشاء إجابات لك...',
3709
+ learnMore: 'تعرف على المزيد',
3710
+ waitingForResponseMsg: 'نرجو منك التحلّي بالصبر، نحن ما زلنا نفكر',
3711
+ isRtl: true,
3712
+ },
3713
+ {
3714
+ prefix: 'KA',
3715
+ dialect: 'ka-GE',
3716
+ englishName: 'Georgian',
3717
+ arabicName: 'الجورجية',
3718
+ nativeName: 'ქართული',
3719
+ detected: 'გამოვლენილი ენა',
3720
+ searching: 'ძიება',
3721
+ elevating: 'ამაღლება',
3722
+ translating: 'თარგმნა',
3723
+ generatingAnswersForYou: 'პასუხების გენერირება თქვენთვის...',
3724
+ learnMore: 'გაიგე მეტი',
3725
+ waitingForResponseMsg: 'გთხოვთ მოითმინოთ, ჯერ კიდევ ვფიქრობთ',
3726
+ isRtl: false,
3727
+ },
3728
+ ]);
3729
+ /**
3730
+ * Lookup by BCP-47 dialect (e.g. `'ar-AE'`). Case-insensitive per BCP-47 §2.1.1
3731
+ * (subtags are case-insensitive on the wire) — accepts `'en-US'`, `'en-us'`,
3732
+ * `'EN-US'`, etc. all as the canonical EN entry. The catalog itself keeps its
3733
+ * canonical casing for display.
3734
+ *
3735
+ * Returns `undefined` when not found; callers decide whether to fall back to `'en-US'`.
3736
+ */
3737
+ function findLocaleByDialect(dialect) {
3738
+ if (typeof dialect !== 'string' || dialect.length === 0)
3739
+ return undefined;
3740
+ const lower = dialect.toLowerCase();
3741
+ return FLY_LOCALE_CATALOG.find((e) => e.dialect.toLowerCase() === lower);
3742
+ }
3743
+ /**
3744
+ * Lookup by short prefix (e.g. `'AR'`). Prefixes are unique in the catalog (asserted by tests).
3745
+ * Case-insensitive — accepts `'AR'`, `'ar'`, `'Ar'`. Catalog stores prefixes uppercase
3746
+ * for display.
3747
+ */
3748
+ function findLocaleByPrefix(prefix) {
3749
+ if (typeof prefix !== 'string' || prefix.length === 0)
3750
+ return undefined;
3751
+ const lower = prefix.toLowerCase();
3752
+ return FLY_LOCALE_CATALOG.find((e) => e.prefix.toLowerCase() === lower);
3753
+ }
3754
+ /**
3755
+ * Type-narrowing helper for entries flagged RTL.
3756
+ *
3757
+ * Named with the `Entry` suffix to avoid colliding with the existing
3758
+ * `isRtlLocale(lang: string)` from `i18n.service.ts`, which answers a different
3759
+ * question (is the *UI* locale code RTL?). Both helpers coexist in `public-api`.
3760
+ */
3761
+ function isRtlLocaleEntry(entry) {
3762
+ return entry.isRtl === true;
3763
+ }
3764
+
2459
3765
  /*
2460
3766
  * @mohamedatia/fly-design-system — Public API
2461
3767
  * https://www.npmjs.com/package/@mohamedatia/fly-design-system
@@ -2526,6 +3832,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
2526
3832
  * `systemKey` field so platform-managed charts (`'apps'`, `'default-company'`) are
2527
3833
  * identifiable across tenants. New `SHARE_ORG_CHART_SYSTEM_KEY_APPS` /
2528
3834
  * `SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT` constants exported.
3835
+ * v2.5.0: Agent input contracts (Phase 1 of the `<fly-agent-input>` plan) — additive only.
3836
+ * `AgentCommandRegistry` + `AgentDropRegistry` (singletons via `providedIn: 'root'`,
3837
+ * shared across federation), `flyAgentDraggable` directive, payload-guard helpers
3838
+ * (`validateAgentPayload`, `trimAgentPayload`, `trimAgentString`, `utf8ByteLength`),
3839
+ * and the cross-cutting `FLY_LOCALE_CATALOG` (31 entries, frozen — verbatim port of
3840
+ * `Fly.Shared.Core.LanguageDTO[]`). Three RTL flagged: `he-IL`, `ur-IN`, `ar-AE`.
3841
+ * The catalog's RTL helper is exported as `isRtlLocaleEntry` to avoid colliding with
3842
+ * the existing `isRtlLocale(lang: string)` from `i18n.service.ts`.
3843
+ * No UI consumers yet — Phase 2 wires the registries into the shell input component.
3844
+ * v2.5.1: New `MessageBoxService.showAcknowledged()` entry point returning
3845
+ * `DialogResultWithAcknowledgement` for "Don't ask again"-style flows.
3846
+ * The `dontAskAgain` config (`MessageBoxDontAskAgainConfig`) lives ONLY on
3847
+ * `MessageBoxOptionsWithAcknowledgement` — the base `MessageBoxOptions`
3848
+ * no longer carries it, so the type system enforces "checkbox ⇒ caller
3849
+ * MUST use `showAcknowledged()`" and eliminates the silent-discard path
3850
+ * on `show()`. Existing `show()` callers are unaffected. Case-insensitive
3851
+ * `findLocaleByDialect` / `findLocaleByPrefix` (BCP-47 §2.1.1). See
3852
+ * `.workflow/plans/agent-input-component.md` Phase 2b-1.
2529
3853
  * See docs/ExternalAppsGuide/03-frontend-app.md.
2530
3854
  */
2531
3855
  /**
@@ -2544,5 +3868,5 @@ const AUDIENCE_ERROR_CODES = {
2544
3868
  * Generated bundle index. Do not edit.
2545
3869
  */
2546
3870
 
2547
- export { AUDIENCE_ERROR_CODES, AUDIENCE_LIMITS, AUDIENCE_PRESETS, AUDIENCE_TERM_KINDS, AudienceBuilderComponent, AuthService, ContextMenuComponent, DEFAULT_FLY_THEME_MODE, DialogResult, FLY_THEME_MODE_IDS, FlyBlockUiComponent, FlyFileUploadComponent, FlyImageUploadComponent, FlyThemeService, I18nService, LAUNCH_CONTEXT, MessageBoxButtons, MessageBoxComponent, MessageBoxIcon, MessageBoxService, MockAuthService, RTL_LOCALE_SET, SHARE_ORG_CHART_SYSTEM_KEY_APPS, SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT, SHARE_PANEL_DEFAULT_FILE_LEVELS, SharePanelComponent, StandaloneWindowManagerService, TranslatePipe, WINDOW_DATA, WindowManagerService, isRtlLocale, normalizeFlyTheme };
3871
+ export { AGENT_DRAG_MIME, AGENT_PAYLOAD_VERSION, AUDIENCE_ERROR_CODES, AUDIENCE_LIMITS, AUDIENCE_PRESETS, AUDIENCE_TERM_KINDS, AgentCommandRegistry, AgentDropRegistry, AgentPayloadOversizeError, AudienceBuilderComponent, AuthService, ContextMenuComponent, DEFAULT_AGENT_PAYLOAD_LIMITS, DEFAULT_FLY_THEME_MODE, DialogResult, FLY_LOCALE_CATALOG, FLY_THEME_MODE_IDS, FlyAgentDraggableDirective, FlyBlockUiComponent, FlyFileUploadComponent, FlyImageUploadComponent, FlyRemoteRouter, FlyThemeService, I18nService, LAUNCH_CONTEXT, MessageBoxButtons, MessageBoxComponent, MessageBoxIcon, MessageBoxService, MockAuthService, RTL_LOCALE_SET, SHARE_ORG_CHART_SYSTEM_KEY_APPS, SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT, SHARE_PANEL_DEFAULT_FILE_LEVELS, SharePanelComponent, StandaloneWindowManagerService, TranslatePipe, WINDOW_DATA, WindowManagerService, findLocaleByDialect, findLocaleByPrefix, isRtlLocale, isRtlLocaleEntry, normalizeFlyTheme, trimAgentPayload, trimAgentString, utf8ByteLength, validateAgentPayload };
2548
3872
  //# sourceMappingURL=mohamedatia-fly-design-system.mjs.map