@flyos/design-system 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, signal, computed, Injectable, inject, TemplateRef, ViewContainerRef, effect, Input, Directive, ErrorHandler, Pipe, ChangeDetectionStrategy, Component, makeEnvironmentProviders, provideAppInitializer, PLATFORM_ID, DestroyRef, ElementRef, input, output, afterNextRender, HostListener, ViewChild, NgZone, viewChild, DOCUMENT as DOCUMENT$1, EventEmitter, Output, forwardRef, model, viewChildren, untracked, Injector, ViewEncapsulation, Renderer2, isDevMode, contentChildren, contentChild } from '@angular/core';
3
- import { catchError, throwError, Subject, from, firstValueFrom, of, ReplaySubject, retry, timer, map } from 'rxjs';
2
+ import { InjectionToken, signal, computed, Injectable, inject, TemplateRef, ViewContainerRef, effect, Input, Directive, ErrorHandler, isDevMode, Pipe, ChangeDetectionStrategy, Component, makeEnvironmentProviders, provideAppInitializer, PLATFORM_ID, DestroyRef, ElementRef, input, output, afterNextRender, HostListener, ViewChild, NgZone, viewChild, DOCUMENT as DOCUMENT$1, EventEmitter, Output, forwardRef, model, viewChildren, untracked, Injector, ViewEncapsulation, Renderer2, contentChildren, contentChild } from '@angular/core';
3
+ import { catchError, throwError, Subject, from, firstValueFrom, of, Observable, map, ReplaySubject, retry, timer } from 'rxjs';
4
4
  import { HttpClient, HttpParams, HttpHeaders, HttpErrorResponse, HttpEventType } from '@angular/common/http';
5
5
  import { switchMap, debounceTime, distinctUntilChanged, filter } from 'rxjs/operators';
6
6
  import { HubConnectionBuilder, HttpTransportType, LogLevel, HubConnectionState } from '@microsoft/signalr';
@@ -48,7 +48,7 @@ import { CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
48
48
  // tools/publish-library.ps1 at bump time, and asserted by the spec beside this file.
49
49
  // Used only for the diagnostic message; the duplicate-instance detection itself is
50
50
  // version-agnostic, so a stale literal misnames a fork rather than hiding one.
51
- const FLY_DS_VERSION = '1.2.0';
51
+ const FLY_DS_VERSION = '1.4.0';
52
52
  const FLY_DS_REGISTRY_KEY = '__FLY_DS_INSTANCES__';
53
53
  /**
54
54
  * Records this design-system instance on the shared `scope` and returns the
@@ -1585,6 +1585,7 @@ const DS_BASELINE_LOCALES = {
1585
1585
  'ui.action.confirm': 'Confirm',
1586
1586
  'ui.action.dismiss': 'Dismiss',
1587
1587
  'ui.action.fewerOptions': 'Fewer options',
1588
+ 'ui.action.open': 'Open',
1588
1589
  'ui.action.retry': 'Retry',
1589
1590
  'ui.action.save': 'Save',
1590
1591
  'ui.action.search': 'Search',
@@ -1594,6 +1595,7 @@ const DS_BASELINE_LOCALES = {
1594
1595
  'ui.form.autoSaving': 'Auto-saving',
1595
1596
  'ui.nav.breadcrumbs': 'Breadcrumbs',
1596
1597
  'ui.nav.home': 'Home',
1598
+ 'ui.nav.modules': 'Modules',
1597
1599
  'ui.nav.selectModule': 'Select a module',
1598
1600
  'ui.nav.sections': 'Sections',
1599
1601
  'ui.nav.switchModule': 'Switch module',
@@ -1931,6 +1933,7 @@ const DS_BASELINE_LOCALES = {
1931
1933
  'ui.action.confirm': 'تأكيد',
1932
1934
  'ui.action.dismiss': 'تجاهل',
1933
1935
  'ui.action.fewerOptions': 'خيارات أقل',
1936
+ 'ui.action.open': 'فتح',
1934
1937
  'ui.action.retry': 'إعادة المحاولة',
1935
1938
  'ui.action.save': 'حفظ',
1936
1939
  'ui.action.search': 'بحث',
@@ -1940,6 +1943,7 @@ const DS_BASELINE_LOCALES = {
1940
1943
  'ui.form.autoSaving': 'حفظ تلقائي',
1941
1944
  'ui.nav.breadcrumbs': 'مسار التنقل',
1942
1945
  'ui.nav.home': 'الرئيسية',
1946
+ 'ui.nav.modules': 'الوحدات',
1943
1947
  'ui.nav.selectModule': 'اختر وحدة',
1944
1948
  'ui.nav.sections': 'الأقسام',
1945
1949
  'ui.nav.switchModule': 'تبديل الوحدة',
@@ -2277,6 +2281,7 @@ const DS_BASELINE_LOCALES = {
2277
2281
  'ui.action.confirm': 'Confirmer',
2278
2282
  'ui.action.dismiss': 'Ignorer',
2279
2283
  'ui.action.fewerOptions': 'Moins d\'options',
2284
+ 'ui.action.open': 'Ouvrir',
2280
2285
  'ui.action.retry': 'Réessayer',
2281
2286
  'ui.action.save': 'Enregistrer',
2282
2287
  'ui.action.search': 'Rechercher',
@@ -2286,6 +2291,7 @@ const DS_BASELINE_LOCALES = {
2286
2291
  'ui.form.autoSaving': 'Enregistrement automatique',
2287
2292
  'ui.nav.breadcrumbs': 'Fil d\'Ariane',
2288
2293
  'ui.nav.home': 'Accueil',
2294
+ 'ui.nav.modules': 'Modules',
2289
2295
  'ui.nav.selectModule': 'Choisir un module',
2290
2296
  'ui.nav.sections': 'Sections',
2291
2297
  'ui.nav.switchModule': 'Changer de module',
@@ -2622,6 +2628,7 @@ const DS_BASELINE_LOCALES = {
2622
2628
  'ui.action.confirm': 'تصدیق کریں',
2623
2629
  'ui.action.dismiss': 'مسترد کریں',
2624
2630
  'ui.action.fewerOptions': 'کم اختیارات',
2631
+ 'ui.action.open': 'کھولیں',
2625
2632
  'ui.action.retry': 'دوبارہ کوشش کریں',
2626
2633
  'ui.action.save': 'محفوظ کریں',
2627
2634
  'ui.action.search': 'تلاش کریں',
@@ -2631,6 +2638,7 @@ const DS_BASELINE_LOCALES = {
2631
2638
  'ui.form.autoSaving': 'خودکار محفوظ ہو رہا ہے',
2632
2639
  'ui.nav.breadcrumbs': 'بریڈکرمب',
2633
2640
  'ui.nav.home': 'ہوم',
2641
+ 'ui.nav.modules': 'ماڈیولز',
2634
2642
  'ui.nav.selectModule': 'ماڈیول منتخب کریں',
2635
2643
  'ui.nav.sections': 'حصے',
2636
2644
  'ui.nav.switchModule': 'ماڈیول تبدیل کریں',
@@ -2689,14 +2697,18 @@ function normalizeLocaleJson(raw) {
2689
2697
  /**
2690
2698
  * Shared I18nService for the shell and Business Apps.
2691
2699
  *
2692
- * **Merge order** (later keys win): DS baseline → shell layer remote bundles
2693
- * in registration order.
2700
+ * **Merge order** (later keys win): DS baseline → remote bundles in registration
2701
+ * order shell layer.
2694
2702
  *
2695
2703
  * - Baseline: {@link DS_BASELINE_LOCALES} — built-in strings for DS components
2696
2704
  * (markdown editor, entity lookup) so they render localized labels even in a
2697
2705
  * standalone consumer that never populated the shell layer. Always overridable.
2698
- * - Shell: `setShellTranslations()` after loading `locale/{lang}.json` and API overrides.
2699
- * - Remotes: `loadBundle()` per manifest `localeBaseUrl`.
2706
+ * - Remotes: `loadBundle()` per manifest `localeBaseUrl`. May ADD any key and may
2707
+ * override the baseline, but cannot redefine a key the shell owns — the dictionary
2708
+ * is global, so allowing that let one remote restyle the whole desktop's wording.
2709
+ * - Shell: `setShellTranslations()` after loading `locale/{lang}.json` and API
2710
+ * overrides. Highest priority, which also makes the per-tenant
2711
+ * `/api/i18n/desktop-shell/{lang}` overrides authoritative over every app.
2700
2712
  */
2701
2713
  class I18nService {
2702
2714
  // NOTE (NG0200 / federation): the ErrorHandler ↔ I18nService cycle is broken on the
@@ -2711,20 +2723,43 @@ class I18nService {
2711
2723
  _bundleOrder = signal([], ...(ngDevMode ? [{ debugName: "_bundleOrder" }] : /* istanbul ignore next */ []));
2712
2724
  _locale = signal('en', ...(ngDevMode ? [{ debugName: "_locale" }] : /* istanbul ignore next */ []));
2713
2725
  _version = signal(0, ...(ngDevMode ? [{ debugName: "_version" }] : /* istanbul ignore next */ []));
2726
+ /** `${bundleId}|${locale}` pairs already reported by {@link warnOnSuppressedShellKeys}. */
2727
+ _warnedCollisions = new Set();
2714
2728
  /** Built-in DS strings for the active locale (falls back to `en` for any
2715
2729
  * locale we don't ship). Lowest-priority layer — always overridable. */
2716
2730
  _baseline = computed(() => DS_BASELINE_LOCALES[this._locale()] ?? DS_BASELINE_LOCALES['en'] ?? {}, ...(ngDevMode ? [{ debugName: "_baseline" }] : /* istanbul ignore next */ []));
2731
+ /**
2732
+ * Merge order, lowest priority first: DS baseline → remote bundles (registration
2733
+ * order) → shell.
2734
+ *
2735
+ * The shell layer is LAST, and that is the whole point. Translations live in one
2736
+ * flat, global dictionary with no per-app scoping, so when remote bundles were
2737
+ * merged last, any remote that happened to define a key the shell also defines
2738
+ * silently rewrote that string for the ENTIRE desktop — every other app included.
2739
+ *
2740
+ * That was not hypothetical: the Circles remote's bundle shares 63 keys with the
2741
+ * shell and differed on 27 of them, one being `common.label.dashboard`, which is
2742
+ * how it came to own the Arabic display name of a Core App it has nothing to do
2743
+ * with. The symptom is near-impossible to chase from the outside — editing the
2744
+ * shell's own `locale/{lang}.json` simply has no effect at runtime, with nothing
2745
+ * logged.
2746
+ *
2747
+ * A remote may still override the DS baseline (bundles are merged after it), which
2748
+ * is the layer explicitly documented as always-overridable, and it may still add
2749
+ * any key the shell does not define. What it can no longer do is redefine a string
2750
+ * the shell owns. {@link loadBundle} warns in dev when a bundle tries.
2751
+ */
2717
2752
  _merged = computed(() => {
2718
2753
  const shell = this._shell();
2719
2754
  const order = this._bundleOrder();
2720
2755
  const bundles = this._bundles();
2721
- let out = { ...this._baseline(), ...shell };
2756
+ let out = { ...this._baseline() };
2722
2757
  for (const id of order) {
2723
2758
  const b = bundles[id];
2724
2759
  if (b)
2725
2760
  out = { ...out, ...b };
2726
2761
  }
2727
- return out;
2762
+ return { ...out, ...shell };
2728
2763
  }, ...(ngDevMode ? [{ debugName: "_merged" }] : /* istanbul ignore next */ []));
2729
2764
  locale = this._locale.asReadonly();
2730
2765
  version = this._version.asReadonly();
@@ -2738,6 +2773,17 @@ class I18nService {
2738
2773
  this._shell.set(data);
2739
2774
  this._locale.set(lang);
2740
2775
  this.bump();
2776
+ // Bundles routinely land BEFORE this — the shell kicks off remote locale loads
2777
+ // and its own fetch together, and the remotes often win the race. Re-checking
2778
+ // here is what makes the diagnostic order-independent; without it the warning
2779
+ // fired only when the shell happened to arrive first, which in practice was
2780
+ // never.
2781
+ const bundles = this._bundles();
2782
+ for (const id of this._bundleOrder()) {
2783
+ const b = bundles[id];
2784
+ if (b)
2785
+ this.warnOnSuppressedShellKeys(id, b);
2786
+ }
2741
2787
  }
2742
2788
  setLanguage(lang) {
2743
2789
  this._locale.set(lang);
@@ -2763,6 +2809,7 @@ class I18nService {
2763
2809
  const data = normalizeLocaleJson(await resp.json());
2764
2810
  if (abortSignal?.aborted)
2765
2811
  return false;
2812
+ this.warnOnSuppressedShellKeys(id, data);
2766
2813
  this._bundles.update((b) => ({ ...b, [id]: data }));
2767
2814
  this._bundleOrder.update((order) => (order.includes(id) ? order : [...order, id]));
2768
2815
  this._locale.set(lang);
@@ -2777,6 +2824,45 @@ class I18nService {
2777
2824
  return false;
2778
2825
  }
2779
2826
  }
2827
+ /**
2828
+ * Dev-only: report keys a bundle defines that the shell already owns.
2829
+ *
2830
+ * These are now silently ignored (the shell wins), so without this the app would
2831
+ * simply not use strings the remote shipped and nobody would know which ones. The
2832
+ * previous precedence was worse — it applied them, changing wording across the
2833
+ * whole desktop, also silently. Either way the failure is invisible, which is the
2834
+ * argument for saying it out loud exactly once, when the bundle loads.
2835
+ *
2836
+ * A hit is not automatically a bug: an app legitimately reuses shared keys it does
2837
+ * not intend to redefine, so only keys whose VALUE differs are worth reporting.
2838
+ * The fix on the app's side is to namespace the key under its own app id.
2839
+ */
2840
+ warnOnSuppressedShellKeys(id, data) {
2841
+ if (!isDevMode())
2842
+ return;
2843
+ const shell = this._shell();
2844
+ // Nothing to conflict with yet — a standalone consumer, or a bundle that raced
2845
+ // ahead of setShellTranslations. Not a collision, so stay quiet; the shell
2846
+ // re-runs this check for every stored bundle once it lands.
2847
+ if (Object.keys(shell).length === 0)
2848
+ return;
2849
+ const suppressed = Object.keys(data).filter((k) => k in shell && shell[k] !== data[k]);
2850
+ if (suppressed.length === 0)
2851
+ return;
2852
+ // Both entry points can reach the same (bundle, locale) pair — a bundle stored
2853
+ // before the shell is checked again when the shell lands, and every locale
2854
+ // switch replays both. Report each pair once so a real signal doesn't drown in
2855
+ // repeats.
2856
+ const seen = `${id}|${this._locale()}`;
2857
+ if (this._warnedCollisions.has(seen))
2858
+ return;
2859
+ this._warnedCollisions.add(seen);
2860
+ const sample = suppressed.slice(0, 10).join(', ');
2861
+ const more = suppressed.length > 10 ? `, …and ${suppressed.length - 10} more` : '';
2862
+ console.warn(`[I18n] Locale bundle "${id}" redefines ${suppressed.length} key(s) the shell owns; ` +
2863
+ `the shell's values win and the bundle's are ignored. Namespace these under "${id}." ` +
2864
+ `if they are meant to be app-specific: ${sample}${more}`);
2865
+ }
2780
2866
  removeBundle(id) {
2781
2867
  this._bundles.update((b) => {
2782
2868
  const next = { ...b };
@@ -3986,6 +4072,290 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
3986
4072
  args: [{ providedIn: 'root' }]
3987
4073
  }] });
3988
4074
 
4075
+ /** `POST /api/users/resolve` accepts at most this many ids per call (platform contract). */
4076
+ const RESOLVE_BATCH_SIZE = 200;
4077
+ /**
4078
+ * **`FlyUserDirectoryService`** — the one id→person adapter for the platform user
4079
+ * directory (`GET /api/users/lookup?q=`, `POST /api/users/resolve`).
4080
+ *
4081
+ * Every app that renders a *list* of people — comment authors, contributors,
4082
+ * approvers, credits, audit rows — needs the same thing: turn a `userId` into a
4083
+ * name without issuing one request per row. Before this service each app wrote
4084
+ * its own copy (Circles `UserResolverService`, Thoughts `UserLookupService`,
4085
+ * plus hand-rolled `/api/users/resolve` calls in the shell and admin portal),
4086
+ * and every copy re-derived the same read-through cache. This is that cache,
4087
+ * once, in the design system.
4088
+ *
4089
+ * The HTTP call rides the **host's** `HttpClient`, so the host's gateway routing,
4090
+ * auth interceptor, and correlation headers all apply — the same arrangement
4091
+ * `fly-entity-lookup` uses. The DS never configures a base URL of its own.
4092
+ *
4093
+ * ## `label()` is read-through — hosts do not prime
4094
+ *
4095
+ * {@link label} is a *signal-backed* accessor: on a cache miss it queues the id
4096
+ * and returns `null`, then the queued ids are resolved in ONE batched request a
4097
+ * microtask later. Because the read happens inside Angular's reactive context,
4098
+ * a template calling `label(id)` re-renders by itself when the answer lands —
4099
+ * no `computed` shadow state, no `prime()` call at each fetch site.
4100
+ *
4101
+ * That last point is the whole reason this exists. The prime-then-read shape it
4102
+ * replaces required every host to (a) keep the raw page separate from the mapped
4103
+ * page, (b) derive the mapped page through a `computed`, and (c) remember to
4104
+ * prime on *every* fetch path — root load, load-more, load-replies, refetch. Miss
4105
+ * one path and that path silently renders GUIDs, with nothing failing. Making the
4106
+ * cache read-through deletes the whole bug class.
4107
+ *
4108
+ * ## Failure is graceful, and never a retry storm
4109
+ *
4110
+ * A failed lookup caches nothing, so callers fall back to whatever they render
4111
+ * for an unresolved id (typically the raw id). Deliberate asymmetry in retry
4112
+ * behaviour, because a render-driven read must not hammer a broken endpoint:
4113
+ *
4114
+ * - **Render-driven** ({@link label}) attempts an id **once**. After a failure it
4115
+ * stays unresolved for the session rather than re-requesting on every
4116
+ * change-detection pass.
4117
+ * - **Explicit** ({@link prime}, {@link resolve}, {@link invalidate}) always
4118
+ * re-attempts. A host re-fetching a page therefore retries naturally, and a
4119
+ * retry button can call {@link invalidate}.
4120
+ *
4121
+ * Unknown / cross-tenant ids are omitted by the endpoint rather than erroring, so
4122
+ * they simply never resolve — which is also the correct behaviour for a
4123
+ * standalone External App where `/api/users/*` isn't reachable at all.
4124
+ *
4125
+ * @example Comment thread — one binding, no plumbing
4126
+ * ```html
4127
+ * <fly-comment-thread
4128
+ * [rootPage]="rootPage()"
4129
+ * [resolveDisplayName]="directory.label" />
4130
+ * ```
4131
+ * ```ts
4132
+ * protected readonly directory = inject(FlyUserDirectoryService);
4133
+ * ```
4134
+ *
4135
+ * @example People picker — search + prefill
4136
+ * ```html
4137
+ * <fly-people-picker
4138
+ * [searchFn]="directory.search()"
4139
+ * [initialSelection]="selected()" />
4140
+ * ```
4141
+ */
4142
+ class FlyUserDirectoryService {
4143
+ http = inject(HttpClient);
4144
+ /** id → person, for everything resolved so far. Signal-backed so {@link label} is reactive. */
4145
+ entries = signal({}, ...(ngDevMode ? [{ debugName: "entries" }] : /* istanbul ignore next */ []));
4146
+ /** Ids with a request in the air — so a re-render cannot duplicate an in-flight lookup. */
4147
+ inFlight = new Set();
4148
+ /**
4149
+ * Ids {@link label} has already attempted at least once. Checked ONLY on the
4150
+ * render-driven path: it is what stops a persistently failing endpoint from
4151
+ * being re-requested on every change-detection pass. Explicit callers ignore it.
4152
+ */
4153
+ attempted = new Set();
4154
+ /** Ids queued by {@link label}, awaiting the batched microtask flush. */
4155
+ queued = new Set();
4156
+ flushScheduled = false;
4157
+ // ── Read ─────────────────────────────────────────────────────────────────────
4158
+ /**
4159
+ * Display name for `id`, or `null` when it is not (yet) resolved — on a miss the
4160
+ * id is queued for the next batched resolve. Null rather than the raw id on
4161
+ * purpose: a GUID is not a name, and the caller should render its own fallback
4162
+ * (or localized placeholder) instead of the DS guessing one.
4163
+ *
4164
+ * Bound as a value (`[resolveDisplayName]="directory.label"`), so it is an arrow
4165
+ * property rather than a method — `this` stays correct without the host wrapping
4166
+ * it in a closure.
4167
+ */
4168
+ label = (id) => {
4169
+ if (!id)
4170
+ return null;
4171
+ const hit = this.entries()[id];
4172
+ if (hit)
4173
+ return hit.displayName;
4174
+ this.queueForRender(id);
4175
+ return null;
4176
+ };
4177
+ /**
4178
+ * The full resolved person for `id` (name, email, avatar), or `null` when not yet
4179
+ * known. Same read-through queueing as {@link label} — use it when a surface can
4180
+ * render an avatar rather than just a name.
4181
+ */
4182
+ option = (id) => {
4183
+ if (!id)
4184
+ return null;
4185
+ const hit = this.entries()[id];
4186
+ if (hit)
4187
+ return hit;
4188
+ this.queueForRender(id);
4189
+ return null;
4190
+ };
4191
+ // ── Explicit population ──────────────────────────────────────────────────────
4192
+ /**
4193
+ * Eagerly resolves any ids not already known, merging them into the cache.
4194
+ * Optional — {@link label} resolves on demand — but useful to warm the cache
4195
+ * before a render, or to force a retry after a failed auto-attempt (explicit
4196
+ * calls are not subject to the once-only rule; see the class doc).
4197
+ */
4198
+ prime(ids) {
4199
+ const fresh = this.distinct(ids).filter(id => !this.entries()[id] && !this.inFlight.has(id));
4200
+ if (fresh.length === 0)
4201
+ return;
4202
+ this.fetch(fresh);
4203
+ }
4204
+ /**
4205
+ * Bulk-resolves ids to picker options — for prefilling `fly-people-picker`'s
4206
+ * `[initialSelection]` from stored ids. Requests are chunked to the endpoint's
4207
+ * {@link RESOLVE_BATCH_SIZE} cap rather than truncated, so a large selection
4208
+ * resolves fully instead of silently losing its tail. Resolved rows also land in
4209
+ * the cache, so a later {@link label} for the same id is free. Empty in → empty
4210
+ * out with no request.
4211
+ */
4212
+ resolve(ids) {
4213
+ const distinct = this.distinct(ids);
4214
+ if (distinct.length === 0)
4215
+ return of([]);
4216
+ return new Observable(subscriber => {
4217
+ const chunks = this.chunk(distinct);
4218
+ const collected = [];
4219
+ let remaining = chunks.length;
4220
+ let failed = false;
4221
+ for (const batch of chunks) {
4222
+ this.post(batch).subscribe({
4223
+ next: rows => {
4224
+ this.merge(rows);
4225
+ collected.push(...rows);
4226
+ },
4227
+ error: (err) => {
4228
+ if (failed)
4229
+ return;
4230
+ failed = true;
4231
+ subscriber.error(err);
4232
+ },
4233
+ complete: () => {
4234
+ if (failed)
4235
+ return;
4236
+ if (--remaining === 0) {
4237
+ subscriber.next(collected);
4238
+ subscriber.complete();
4239
+ }
4240
+ },
4241
+ });
4242
+ }
4243
+ });
4244
+ }
4245
+ /**
4246
+ * Drops cached entries so the next read re-resolves them — the retry hook for a
4247
+ * host that surfaces a "couldn't load names" affordance. Omit `ids` to clear the
4248
+ * whole cache.
4249
+ */
4250
+ invalidate(ids) {
4251
+ if (!ids) {
4252
+ this.entries.set({});
4253
+ this.inFlight.clear();
4254
+ this.attempted.clear();
4255
+ this.queued.clear();
4256
+ return;
4257
+ }
4258
+ const targets = this.distinct(ids);
4259
+ if (targets.length === 0)
4260
+ return;
4261
+ targets.forEach(id => {
4262
+ this.inFlight.delete(id);
4263
+ this.attempted.delete(id);
4264
+ });
4265
+ this.entries.update(prev => {
4266
+ const next = { ...prev };
4267
+ targets.forEach(id => delete next[id]);
4268
+ return next;
4269
+ });
4270
+ }
4271
+ // ── Search ───────────────────────────────────────────────────────────────────
4272
+ /**
4273
+ * A `searchFn` over the platform users directory — bind straight to
4274
+ * `<fly-people-picker [searchFn]>`. Results are cached too, so picking someone
4275
+ * makes their name available to {@link label} without another round-trip.
4276
+ */
4277
+ search() {
4278
+ return (query) => this.http
4279
+ .get('/api/users/lookup', { params: { q: query } })
4280
+ .pipe(map(res => (res.data ?? []).map(toOption)), map(rows => {
4281
+ this.merge(rows);
4282
+ return rows;
4283
+ }));
4284
+ }
4285
+ // ── Internals ────────────────────────────────────────────────────────────────
4286
+ /** Queues a render-driven miss, honouring the once-only rule. */
4287
+ queueForRender(id) {
4288
+ if (this.entries()[id] || this.inFlight.has(id) || this.attempted.has(id) || this.queued.has(id))
4289
+ return;
4290
+ this.queued.add(id);
4291
+ if (this.flushScheduled)
4292
+ return;
4293
+ this.flushScheduled = true;
4294
+ // A microtask, not a synchronous call: `label()` runs during template
4295
+ // evaluation, and firing the request (and its signal write) inline would
4296
+ // mutate state mid-change-detection. Deferring also batches every miss in
4297
+ // the pass into ONE request — a 20-comment page costs one round-trip.
4298
+ queueMicrotask(() => this.flush());
4299
+ }
4300
+ flush() {
4301
+ this.flushScheduled = false;
4302
+ const batch = [...this.queued];
4303
+ this.queued.clear();
4304
+ if (batch.length === 0)
4305
+ return;
4306
+ batch.forEach(id => this.attempted.add(id));
4307
+ this.fetch(batch);
4308
+ }
4309
+ /** Issues the (chunked) resolve calls for `ids` and merges whatever comes back. */
4310
+ fetch(ids) {
4311
+ for (const batch of this.chunk(ids)) {
4312
+ batch.forEach(id => this.inFlight.add(id));
4313
+ this.post(batch).subscribe({
4314
+ next: rows => this.merge(rows),
4315
+ // Unknown / cross-tenant ids are omitted by the endpoint rather than
4316
+ // erroring, so reaching here means a transport-level failure. Cache
4317
+ // nothing — a wrong answer is worse than an unresolved one.
4318
+ error: () => batch.forEach(id => this.inFlight.delete(id)),
4319
+ complete: () => batch.forEach(id => this.inFlight.delete(id)),
4320
+ });
4321
+ }
4322
+ }
4323
+ post(ids) {
4324
+ return this.http
4325
+ .post('/api/users/resolve', { ids })
4326
+ .pipe(map(res => (res.data ?? []).map(toOption)));
4327
+ }
4328
+ merge(rows) {
4329
+ if (rows.length === 0)
4330
+ return;
4331
+ this.entries.update(prev => {
4332
+ const next = { ...prev };
4333
+ rows.forEach(r => (next[r.id] = r));
4334
+ return next;
4335
+ });
4336
+ }
4337
+ distinct(ids) {
4338
+ return [...new Set(ids.filter((id) => !!id))];
4339
+ }
4340
+ chunk(ids) {
4341
+ const out = [];
4342
+ for (let i = 0; i < ids.length; i += RESOLVE_BATCH_SIZE) {
4343
+ out.push(ids.slice(i, i + RESOLVE_BATCH_SIZE));
4344
+ }
4345
+ return out;
4346
+ }
4347
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyUserDirectoryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4348
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyUserDirectoryService, providedIn: 'root' });
4349
+ }
4350
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyUserDirectoryService, decorators: [{
4351
+ type: Injectable,
4352
+ args: [{ providedIn: 'root' }]
4353
+ }] });
4354
+ /** `UserLookupDto`/`UserResolvedDto` → the picker option shape, 1:1. */
4355
+ function toOption(u) {
4356
+ return { id: u.id, displayName: u.displayName, email: u.email, avatarUrl: u.avatarUrl };
4357
+ }
4358
+
3989
4359
  /**
3990
4360
  * The page's CSP nonce, read once at module init from `<meta name="csp-nonce">`.
3991
4361
  *
@@ -5363,6 +5733,22 @@ const MARGIN = 4;
5363
5733
  * host's `getBoundingClientRect()`, so it escapes every overflow-clip and app-window
5364
5734
  * bound. It flips placement + clamps to the viewport so it's always fully visible.
5365
5735
  *
5736
+ * ## `data-tooltip` is the SAME primitive now (the global clipping fix)
5737
+ * `[data-tooltip]` used to be a CSS-only `::before`/`::after` pair in
5738
+ * `_app-surface-utilities.scss`. A pseudo-element is a child box of its host, so it is
5739
+ * clipped by ANY scrolling / `overflow: hidden` ancestor — which is why the boards
5740
+ * toolbar rail (a `overflow-y: auto` plate) rendered every tool tip cut off inside its
5741
+ * own 47px-wide column. No z-index can fix that: clipping is not a stacking question.
5742
+ * `position: fixed` on the pseudo would not fix it either, because the same plates carry
5743
+ * `backdrop-filter`, and a filtered ancestor is a containing block for fixed descendants.
5744
+ *
5745
+ * The fix is to have ONE tooltip implementation, and it is this one: the selector now
5746
+ * also matches `[data-tooltip]`, and when no `flyTooltip` text is bound the directive
5747
+ * reads the host's `data-tooltip` attribute at show time. Reading it lazily (rather than
5748
+ * binding it) is what lets components that write the attribute IMPERATIVELY — the DS icon
5749
+ * button resolves an i18n key into it — participate by simply listing this directive in
5750
+ * `hostDirectives`, with no input to keep in sync.
5751
+ *
5366
5752
  * ## Behaviour
5367
5753
  * - Shows on `mouseenter` AND `focus` (keyboard users), after `flyTooltipDelay` ms.
5368
5754
  * - Hides immediately on `mouseleave` / `blur` / `Escape` / scroll / wheel / destroy.
@@ -5429,9 +5815,19 @@ class FlyTooltipDirective {
5429
5815
  // Belt-and-braces alongside DestroyRef (a plain `new` in a test won't run the effect).
5430
5816
  this.hide();
5431
5817
  }
5432
- /** Trimmed text, or `''` when there's nothing meaningful to show. */
5818
+ /**
5819
+ * Trimmed text, or `''` when there's nothing meaningful to show.
5820
+ *
5821
+ * Falls back to the host's `data-tooltip` attribute — read from the DOM rather than through an
5822
+ * input, because the DS icon button writes it imperatively from a resolved i18n key (and re-writes
5823
+ * it on every locale change). A signal input could not see that; an attribute read at show time
5824
+ * always reports the current label.
5825
+ */
5433
5826
  normalizedText() {
5434
- return (this.text() ?? '').trim();
5827
+ const bound = (this.text() ?? '').trim();
5828
+ if (bound)
5829
+ return bound;
5830
+ return (this.host.nativeElement.getAttribute('data-tooltip') ?? '').trim();
5435
5831
  }
5436
5832
  scheduleShow() {
5437
5833
  if (this.flyTooltipDisabled() || !this.normalizedText())
@@ -5599,12 +5995,12 @@ class FlyTooltipDirective {
5599
5995
  this.doc.head.appendChild(style);
5600
5996
  }
5601
5997
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyTooltipDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
5602
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.19", type: FlyTooltipDirective, isStandalone: true, selector: "[flyTooltip]", inputs: { text: { classPropertyName: "text", publicName: "flyTooltip", isSignal: true, isRequired: false, transformFunction: null }, flyTooltipPlacement: { classPropertyName: "flyTooltipPlacement", publicName: "flyTooltipPlacement", isSignal: true, isRequired: false, transformFunction: null }, flyTooltipDisabled: { classPropertyName: "flyTooltipDisabled", publicName: "flyTooltipDisabled", isSignal: true, isRequired: false, transformFunction: null }, flyTooltipDelay: { classPropertyName: "flyTooltipDelay", publicName: "flyTooltipDelay", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "mouseenter": "scheduleShow()", "focus": "scheduleShow()", "mouseleave": "hide()", "blur": "hide()" } }, ngImport: i0 });
5998
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.19", type: FlyTooltipDirective, isStandalone: true, selector: "[flyTooltip], [data-tooltip]", inputs: { text: { classPropertyName: "text", publicName: "flyTooltip", isSignal: true, isRequired: false, transformFunction: null }, flyTooltipPlacement: { classPropertyName: "flyTooltipPlacement", publicName: "flyTooltipPlacement", isSignal: true, isRequired: false, transformFunction: null }, flyTooltipDisabled: { classPropertyName: "flyTooltipDisabled", publicName: "flyTooltipDisabled", isSignal: true, isRequired: false, transformFunction: null }, flyTooltipDelay: { classPropertyName: "flyTooltipDelay", publicName: "flyTooltipDelay", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "mouseenter": "scheduleShow()", "focus": "scheduleShow()", "mouseleave": "hide()", "blur": "hide()" } }, ngImport: i0 });
5603
5999
  }
5604
6000
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyTooltipDirective, decorators: [{
5605
6001
  type: Directive,
5606
6002
  args: [{
5607
- selector: '[flyTooltip]',
6003
+ selector: '[flyTooltip], [data-tooltip]',
5608
6004
  standalone: true,
5609
6005
  host: {
5610
6006
  '(mouseenter)': 'scheduleShow()',
@@ -6919,7 +7315,7 @@ class FlyEmojiPickerComponent {
6919
7315
  this.selectionChange.emit(next);
6920
7316
  }
6921
7317
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyEmojiPickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6922
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyEmojiPickerComponent, isStandalone: true, selector: "fly-emoji-picker", inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, selectionMode: { classPropertyName: "selectionMode", publicName: "selectionMode", isSignal: true, isRequired: false, transformFunction: null }, selectedIds: { classPropertyName: "selectedIds", publicName: "selectedIds", isSignal: true, isRequired: false, transformFunction: null }, categories: { classPropertyName: "categories", publicName: "categories", isSignal: true, isRequired: false, transformFunction: null }, defaultCategory: { classPropertyName: "defaultCategory", publicName: "defaultCategory", isSignal: true, isRequired: false, transformFunction: null }, skinTones: { classPropertyName: "skinTones", publicName: "skinTones", isSignal: true, isRequired: false, transformFunction: null }, skinTone: { classPropertyName: "skinTone", publicName: "skinTone", isSignal: true, isRequired: false, transformFunction: null }, showMore: { classPropertyName: "showMore", publicName: "showMore", isSignal: true, isRequired: false, transformFunction: null }, previewCount: { classPropertyName: "previewCount", publicName: "previewCount", isSignal: true, isRequired: false, transformFunction: null }, emojiAnimation: { classPropertyName: "emojiAnimation", publicName: "emojiAnimation", isSignal: true, isRequired: false, transformFunction: null }, dismissOnOutsidePress: { classPropertyName: "dismissOnOutsidePress", publicName: "dismissOnOutsidePress", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { stickerPicked: "stickerPicked", toggled: "toggled", selectionChange: "selectionChange", skinToneChange: "skinToneChange", dismissed: "dismissed" }, host: { properties: { "class.fly-emoji-picker--small": "size() === 'small'", "class.fly-emoji-picker--medium": "size() === 'medium'", "class.fly-emoji-picker--large": "size() === 'large'" } }, viewQueries: [{ propertyName: "searchRef", first: true, predicate: ["searchRef"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\r\n class=\"fly-emoji-picker\"\r\n [flyClickOutsideEnabled]=\"dismissOnOutsidePress()\"\r\n (flyClickOutside)=\"dismissed.emit()\"\r\n [attr.aria-label]=\"'common.label.emoji_picker' | translate\">\r\n <!-- Stickers mode only: the emoji grid is unchanged, stickers are an ADDITIONAL surface. -->\r\n @if (showTabs()) {\r\n <div\r\n class=\"fly-emoji-picker__tabs\"\r\n role=\"tablist\"\r\n [attr.aria-label]=\"'common.label.emoji_tabs' | translate\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__tab\"\r\n role=\"tab\"\r\n id=\"fly-emoji-tab-emoji\"\r\n aria-controls=\"fly-emoji-panel-emoji\"\r\n [class.is-active]=\"activeTab() === 'emoji'\"\r\n [attr.aria-selected]=\"activeTab() === 'emoji'\"\r\n [attr.tabindex]=\"activeTab() === 'emoji' ? 0 : -1\"\r\n (keydown)=\"onTabKeydown($event)\"\r\n (click)=\"selectTab('emoji')\">\r\n {{ 'common.label.emoji_tab_emoji' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__tab\"\r\n role=\"tab\"\r\n id=\"fly-emoji-tab-stickers\"\r\n aria-controls=\"fly-emoji-panel-stickers\"\r\n [class.is-active]=\"activeTab() === 'stickers'\"\r\n [attr.aria-selected]=\"activeTab() === 'stickers'\"\r\n [attr.tabindex]=\"activeTab() === 'stickers' ? 0 : -1\"\r\n (keydown)=\"onTabKeydown($event)\"\r\n (click)=\"selectTab('stickers')\">\r\n {{ 'common.label.emoji_tab_stickers' | translate }}\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (showTabs() && activeTab() === 'stickers') {\r\n <div\r\n class=\"fly-emoji-picker__stickers\"\r\n id=\"fly-emoji-panel-stickers\"\r\n role=\"tabpanel\"\r\n aria-labelledby=\"fly-emoji-tab-stickers\">\r\n @for (entry of stickers(); track entry.id) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__sticker\"\r\n [flyTooltip]=\"entry.label\"\r\n [attr.aria-label]=\"entry.label\"\r\n (pointerenter)=\"onCellHover(sticker)\"\r\n (pointerleave)=\"cancelHover()\"\r\n (click)=\"pickSticker(entry)\">\r\n <fly-animated-emoji #sticker [id]=\"entry.id\" [size]=\"64\" />\r\n </button>\r\n } @empty {\r\n <p class=\"fly-emoji-picker__empty\">{{ 'agent.lookup.no_results' | translate }}</p>\r\n }\r\n </div>\r\n }\r\n\r\n @if (!showTabs() || activeTab() === 'emoji') {\r\n <!-- Search + tone sit together as one chrome bar: the tone swatches are hands, and on their own row\r\n above the grid they read as pickable content rather than a setting. -->\r\n <div class=\"fly-emoji-picker__bar\">\r\n <input\r\n class=\"fly-emoji-picker__search\"\r\n type=\"text\"\r\n #searchRef\r\n [value]=\"query()\"\r\n (input)=\"onQueryInput(searchRef.value)\"\r\n [placeholder]=\"'common.label.search_emoji' | translate\"\r\n [attr.aria-label]=\"'common.label.search_emoji' | translate\"\r\n spellcheck=\"false\"\r\n autocomplete=\"off\" />\r\n\r\n @if (showToneStrip()) {\r\n <!-- Escape-to-close listener only \u2014 every actual control inside (trigger + options) is its\r\n own focusable button, so this wrapper itself doesn't need to be. -->\r\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\r\n <div\r\n class=\"fly-emoji-picker__tones\"\r\n [flyClickOutsideEnabled]=\"toneMenuOpen()\"\r\n (flyClickOutside)=\"toneMenuOpen.set(false)\"\r\n (keydown.escape)=\"toneMenuOpen.set(false)\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__tone-trigger\"\r\n aria-haspopup=\"listbox\"\r\n [attr.aria-expanded]=\"toneMenuOpen()\"\r\n [attr.aria-label]=\"'common.label.skin_tone' | translate\"\r\n [flyTooltip]=\"toneLabelKey(effectiveTone()) | translate\"\r\n (click)=\"toneMenuOpen.set(!toneMenuOpen())\">\r\n <span class=\"fly-emoji-picker__tone-glyph\">{{ toneSwatch(effectiveTone()) }}</span>\r\n <i class=\"pi pi-chevron-down fly-emoji-picker__tone-caret\" aria-hidden=\"true\"></i>\r\n </button>\r\n\r\n @if (toneMenuOpen()) {\r\n <div\r\n class=\"fly-emoji-picker__tone-menu\"\r\n role=\"listbox\"\r\n [attr.aria-label]=\"'common.label.skin_tone' | translate\">\r\n @for (tone of tones; track tone) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__tone-option\"\r\n role=\"option\"\r\n [class.is-active]=\"effectiveTone() === tone\"\r\n [attr.aria-selected]=\"effectiveTone() === tone\"\r\n [flyTooltip]=\"toneLabelKey(tone) | translate\"\r\n (click)=\"onToneOptionClick(tone)\">\r\n {{ toneSwatch(tone) }}\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- ONE scrolling list; each category is a separator label over its own grid. Tabs wrapped to three\r\n rows in a sidebar drawer and cost a click to reach any group. -->\r\n <div\r\n class=\"fly-emoji-picker__list\"\r\n role=\"listbox\"\r\n [attr.id]=\"showTabs() ? 'fly-emoji-panel-emoji' : null\"\r\n [attr.aria-labelledby]=\"showTabs() ? 'fly-emoji-tab-emoji' : null\"\r\n [attr.aria-multiselectable]=\"selectionMode() === 'multi'\">\r\n @for (section of sections(); track section.id) {\r\n <div class=\"fly-emoji-picker__section\" role=\"group\" [attr.aria-label]=\"sectionLabel(section)\">\r\n <h4 class=\"fly-emoji-picker__heading\">{{ sectionLabel(section) }}</h4>\r\n <div class=\"fly-emoji-picker__grid\">\r\n @for (cell of section.cells; track cell.entry.id) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__cell\"\r\n role=\"option\"\r\n [class.is-active]=\"cell.selected\"\r\n [attr.aria-selected]=\"cell.selected\"\r\n [flyTooltip]=\"cell.entry.label\"\r\n [attr.aria-label]=\"cell.entry.label\"\r\n (click)=\"pick(cell.entry)\">\r\n @if (section.animated) {\r\n <!-- Hover lives on the animated element, not the button: a template reference\r\n declared inside a control-flow block isn't visible to the block's parent. -->\r\n <fly-animated-emoji\r\n #anim\r\n class=\"fly-emoji-picker__glyph\"\r\n [id]=\"cell.entry.id\"\r\n [size]=\"20\"\r\n (pointerenter)=\"onCellHover(anim)\"\r\n (pointerleave)=\"cancelHover()\" />\r\n } @else {\r\n <span class=\"fly-emoji-picker__glyph\">{{ cell.glyph }}</span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n } @empty {\r\n <p class=\"fly-emoji-picker__empty\">{{ 'agent.lookup.no_results' | translate }}</p>\r\n }\r\n\r\n <!-- Lives INSIDE the scroller, as the last row, rather than pinned below it \u2014 a static\r\n footer row permanently ate into the vertical space actual emoji rows could use;\r\n scrolled into view instead, it only costs space once you reach the end. -->\r\n @if (hasMore()) {\r\n <button type=\"button\" class=\"fly-emoji-picker__more\" (click)=\"toggleExpanded()\">\r\n {{ (isExpanded() ? 'common.label.emoji_show_less' : 'common.label.emoji_show_more') | translate }}\r\n </button>\r\n }\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block;block-size:100%;-webkit-backdrop-filter:blur(24px) saturate(130%);backdrop-filter:blur(24px) saturate(130%);--fly-emoji-cell: 36px;--fly-emoji-cell-gap: 6px;--fly-emoji-glyph: 17px;--fly-emoji-section-gap: 14px;--fly-emoji-bar-gap: 8px;--fly-emoji-search-font: 12px;--fly-emoji-search-padding: 8px 10px;--fly-emoji-heading-display: block}:host(.fly-emoji-picker--medium){--fly-emoji-cell: 30px;--fly-emoji-cell-gap: 5px;--fly-emoji-glyph: 16px;--fly-emoji-section-gap: 10px;--fly-emoji-bar-gap: 6px;--fly-emoji-search-font: 11.5px;--fly-emoji-search-padding: 6px 9px}:host(.fly-emoji-picker--small){--fly-emoji-cell: 24px;--fly-emoji-cell-gap: 3px;--fly-emoji-glyph: 15px;--fly-emoji-section-gap: 6px;--fly-emoji-bar-gap: 5px;--fly-emoji-search-font: 11px;--fly-emoji-search-padding: 4px 7px;--fly-emoji-heading-display: none;--fly-emoji-picker-list-max: 132px}.fly-emoji-picker{display:flex;flex-direction:column;gap:var(--fly-emoji-bar-gap);box-sizing:border-box;block-size:100%;color:var(--text-color, inherit)}.fly-emoji-picker__bar{display:flex;align-items:center;gap:10px}.fly-emoji-picker__search{flex:1 1 auto;min-inline-size:0;box-sizing:border-box;padding:var(--fly-emoji-search-padding);font:inherit;font-size:var(--fly-emoji-search-font);border:1px solid var(--surface-border, rgba(255, 255, 255, .12));border-radius:5px;background:transparent;color:var(--text-color, inherit);outline:none}.fly-emoji-picker__search:focus{border-color:var(--primary-color)}.fly-emoji-picker__list{display:flex;flex:1 1 auto;flex-direction:column;gap:var(--fly-emoji-section-gap);min-block-size:0;max-block-size:var(--fly-emoji-picker-list-max, 320px);overflow-y:auto;padding-inline-end:4px}.fly-emoji-picker__section{display:flex;flex-direction:column;gap:6px}.fly-emoji-picker__heading{display:var(--fly-emoji-heading-display);position:sticky;inset-block-start:0;z-index:1;margin:0;padding-block:3px;font-size:10px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:var(--text-color-secondary, rgba(235, 235, 245, .55));background:var(--surface-overlay, var(--surface-card, #1c1c1e))}.fly-emoji-picker__tones{position:relative;flex:0 0 auto}.fly-emoji-picker__tone-trigger{display:flex;align-items:center;gap:2px;inline-size:auto;block-size:22px;padding:0 4px;font-size:14px;line-height:1;border:1px solid var(--surface-border, rgba(255, 255, 255, .12));border-radius:5px;background:transparent;color:var(--text-color-secondary, rgba(235, 235, 245, .55));cursor:pointer;transition:background .1s ease,border-color .1s ease}.fly-emoji-picker__tone-trigger:hover{background:var(--surface-hover, rgba(255, 255, 255, .08))}.fly-emoji-picker__tone-trigger:focus-visible{outline:2px solid var(--primary-color);outline-offset:-1px}.fly-emoji-picker__tone-glyph{line-height:1}.fly-emoji-picker__tone-caret{font-size:8px}.fly-emoji-picker__tone-menu{position:absolute;inset-block-start:calc(100% + 4px);inset-inline-end:0;z-index:21;display:grid;grid-template-columns:repeat(3,1fr);gap:2px;padding:4px;background:var(--surface-overlay-strong, var(--surface-overlay, var(--surface-card, #1c1c1e)));border:1px solid var(--surface-border, rgba(255, 255, 255, .12));border-radius:8px;box-shadow:0 8px 24px #00000047}.fly-emoji-picker__tone-option{display:grid;place-items:center;inline-size:26px;block-size:26px;padding:0;font-size:15px;line-height:1;border:1px solid transparent;border-radius:5px;background:transparent;cursor:pointer;transition:background .1s ease,border-color .1s ease}.fly-emoji-picker__tone-option:hover{background:var(--surface-hover, rgba(255, 255, 255, .08))}.fly-emoji-picker__tone-option:focus-visible{outline:2px solid var(--primary-color);outline-offset:-1px}.fly-emoji-picker__tone-option.is-active{border-color:var(--primary-color);background:var(--surface-hover, rgba(255, 255, 255, .1))}.fly-emoji-picker__grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(var(--fly-emoji-cell),1fr));gap:var(--fly-emoji-cell-gap)}.fly-emoji-picker__cell{display:flex;align-items:center;justify-content:center;aspect-ratio:1;min-block-size:var(--fly-emoji-cell);padding:4px;border:1px solid transparent;border-radius:6px;background:transparent;color:var(--text-color, inherit);cursor:pointer;transition:background .1s ease,border-color .1s ease}.fly-emoji-picker__cell:hover{background:var(--surface-hover, rgba(255, 255, 255, .08));border-color:var(--surface-border, rgba(255, 255, 255, .12))}.fly-emoji-picker__cell:focus-visible{outline:2px solid var(--primary-color);outline-offset:-1px}.fly-emoji-picker__cell.is-active{border-color:var(--primary-color);background:var(--selection-bg, rgba(0, 113, 227, .24))}.fly-emoji-picker__glyph{direction:ltr;font-size:var(--fly-emoji-glyph);line-height:1}.fly-emoji-picker__more{align-self:flex-start;padding:3px 8px;font:inherit;font-size:11px;border:none;border-radius:999px;background:transparent;color:var(--primary-color);cursor:pointer}.fly-emoji-picker__more:hover{background:var(--surface-hover, rgba(255, 255, 255, .06))}.fly-emoji-picker__more:focus-visible{outline:2px solid var(--primary-color);outline-offset:1px}.fly-emoji-picker__empty{margin:0;padding:12px 4px;text-align:center;font-size:12px;color:var(--text-color-secondary, rgba(235, 235, 245, .5))}.fly-emoji-picker__tabs{display:flex;gap:4px;border-block-end:1px solid var(--surface-border, rgba(255, 255, 255, .12))}.fly-emoji-picker__tab{appearance:none;padding:6px 12px;font:inherit;font-size:12px;border:none;border-block-end:2px solid transparent;background:transparent;color:var(--text-color-secondary, rgba(235, 235, 245, .6));cursor:pointer}.fly-emoji-picker__tab.is-active{color:var(--text-color, inherit);border-block-end-color:var(--primary-color)}.fly-emoji-picker__tab:focus-visible{outline:2px solid var(--primary-color);outline-offset:-2px}.fly-emoji-picker__stickers{flex:1 1 auto;min-block-size:0;overflow-y:auto;display:grid;grid-template-columns:repeat(auto-fill,minmax(72px,1fr));gap:6px;padding:4px}.fly-emoji-picker__sticker{appearance:none;display:flex;align-items:center;justify-content:center;padding:4px;border:none;border-radius:10px;background:transparent;cursor:pointer}.fly-emoji-picker__sticker:hover,.fly-emoji-picker__sticker:focus-visible{background:var(--surface-hover, rgba(255, 255, 255, .08))}.fly-emoji-picker__sticker:focus-visible{outline:2px solid var(--primary-color);outline-offset:-2px}\n"], dependencies: [{ kind: "directive", type: FlyClickOutsideDirective, selector: "[flyClickOutside]", inputs: ["flyClickOutsideEnabled"], outputs: ["flyClickOutside"] }, { kind: "directive", type: FlyTooltipDirective, selector: "[flyTooltip]", inputs: ["flyTooltip", "flyTooltipPlacement", "flyTooltipDisabled", "flyTooltipDelay"] }, { kind: "component", type: FlyAnimatedEmojiComponent, selector: "fly-animated-emoji", inputs: ["id", "size", "autoplayOnFirstVisible", "playOnHover"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7318
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyEmojiPickerComponent, isStandalone: true, selector: "fly-emoji-picker", inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, selectionMode: { classPropertyName: "selectionMode", publicName: "selectionMode", isSignal: true, isRequired: false, transformFunction: null }, selectedIds: { classPropertyName: "selectedIds", publicName: "selectedIds", isSignal: true, isRequired: false, transformFunction: null }, categories: { classPropertyName: "categories", publicName: "categories", isSignal: true, isRequired: false, transformFunction: null }, defaultCategory: { classPropertyName: "defaultCategory", publicName: "defaultCategory", isSignal: true, isRequired: false, transformFunction: null }, skinTones: { classPropertyName: "skinTones", publicName: "skinTones", isSignal: true, isRequired: false, transformFunction: null }, skinTone: { classPropertyName: "skinTone", publicName: "skinTone", isSignal: true, isRequired: false, transformFunction: null }, showMore: { classPropertyName: "showMore", publicName: "showMore", isSignal: true, isRequired: false, transformFunction: null }, previewCount: { classPropertyName: "previewCount", publicName: "previewCount", isSignal: true, isRequired: false, transformFunction: null }, emojiAnimation: { classPropertyName: "emojiAnimation", publicName: "emojiAnimation", isSignal: true, isRequired: false, transformFunction: null }, dismissOnOutsidePress: { classPropertyName: "dismissOnOutsidePress", publicName: "dismissOnOutsidePress", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { stickerPicked: "stickerPicked", toggled: "toggled", selectionChange: "selectionChange", skinToneChange: "skinToneChange", dismissed: "dismissed" }, host: { properties: { "class.fly-emoji-picker--small": "size() === 'small'", "class.fly-emoji-picker--medium": "size() === 'medium'", "class.fly-emoji-picker--large": "size() === 'large'" } }, viewQueries: [{ propertyName: "searchRef", first: true, predicate: ["searchRef"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\r\n class=\"fly-emoji-picker\"\r\n [flyClickOutsideEnabled]=\"dismissOnOutsidePress()\"\r\n (flyClickOutside)=\"dismissed.emit()\"\r\n [attr.aria-label]=\"'common.label.emoji_picker' | translate\">\r\n <!-- Stickers mode only: the emoji grid is unchanged, stickers are an ADDITIONAL surface. -->\r\n @if (showTabs()) {\r\n <div\r\n class=\"fly-emoji-picker__tabs\"\r\n role=\"tablist\"\r\n [attr.aria-label]=\"'common.label.emoji_tabs' | translate\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__tab\"\r\n role=\"tab\"\r\n id=\"fly-emoji-tab-emoji\"\r\n aria-controls=\"fly-emoji-panel-emoji\"\r\n [class.is-active]=\"activeTab() === 'emoji'\"\r\n [attr.aria-selected]=\"activeTab() === 'emoji'\"\r\n [attr.tabindex]=\"activeTab() === 'emoji' ? 0 : -1\"\r\n (keydown)=\"onTabKeydown($event)\"\r\n (click)=\"selectTab('emoji')\">\r\n {{ 'common.label.emoji_tab_emoji' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__tab\"\r\n role=\"tab\"\r\n id=\"fly-emoji-tab-stickers\"\r\n aria-controls=\"fly-emoji-panel-stickers\"\r\n [class.is-active]=\"activeTab() === 'stickers'\"\r\n [attr.aria-selected]=\"activeTab() === 'stickers'\"\r\n [attr.tabindex]=\"activeTab() === 'stickers' ? 0 : -1\"\r\n (keydown)=\"onTabKeydown($event)\"\r\n (click)=\"selectTab('stickers')\">\r\n {{ 'common.label.emoji_tab_stickers' | translate }}\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (showTabs() && activeTab() === 'stickers') {\r\n <div\r\n class=\"fly-emoji-picker__stickers\"\r\n id=\"fly-emoji-panel-stickers\"\r\n role=\"tabpanel\"\r\n aria-labelledby=\"fly-emoji-tab-stickers\">\r\n @for (entry of stickers(); track entry.id) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__sticker\"\r\n [flyTooltip]=\"entry.label\"\r\n [attr.aria-label]=\"entry.label\"\r\n (pointerenter)=\"onCellHover(sticker)\"\r\n (pointerleave)=\"cancelHover()\"\r\n (click)=\"pickSticker(entry)\">\r\n <fly-animated-emoji #sticker [id]=\"entry.id\" [size]=\"64\" />\r\n </button>\r\n } @empty {\r\n <p class=\"fly-emoji-picker__empty\">{{ 'agent.lookup.no_results' | translate }}</p>\r\n }\r\n </div>\r\n }\r\n\r\n @if (!showTabs() || activeTab() === 'emoji') {\r\n <!-- Search + tone sit together as one chrome bar: the tone swatches are hands, and on their own row\r\n above the grid they read as pickable content rather than a setting. -->\r\n <div class=\"fly-emoji-picker__bar\">\r\n <input\r\n class=\"fly-emoji-picker__search\"\r\n type=\"text\"\r\n #searchRef\r\n [value]=\"query()\"\r\n (input)=\"onQueryInput(searchRef.value)\"\r\n [placeholder]=\"'common.label.search_emoji' | translate\"\r\n [attr.aria-label]=\"'common.label.search_emoji' | translate\"\r\n spellcheck=\"false\"\r\n autocomplete=\"off\" />\r\n\r\n @if (showToneStrip()) {\r\n <!-- Escape-to-close listener only \u2014 every actual control inside (trigger + options) is its\r\n own focusable button, so this wrapper itself doesn't need to be. -->\r\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\r\n <div\r\n class=\"fly-emoji-picker__tones\"\r\n [flyClickOutsideEnabled]=\"toneMenuOpen()\"\r\n (flyClickOutside)=\"toneMenuOpen.set(false)\"\r\n (keydown.escape)=\"toneMenuOpen.set(false)\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__tone-trigger\"\r\n aria-haspopup=\"listbox\"\r\n [attr.aria-expanded]=\"toneMenuOpen()\"\r\n [attr.aria-label]=\"'common.label.skin_tone' | translate\"\r\n [flyTooltip]=\"toneLabelKey(effectiveTone()) | translate\"\r\n (click)=\"toneMenuOpen.set(!toneMenuOpen())\">\r\n <span class=\"fly-emoji-picker__tone-glyph\">{{ toneSwatch(effectiveTone()) }}</span>\r\n <i class=\"pi pi-chevron-down fly-emoji-picker__tone-caret\" aria-hidden=\"true\"></i>\r\n </button>\r\n\r\n @if (toneMenuOpen()) {\r\n <div\r\n class=\"fly-emoji-picker__tone-menu\"\r\n role=\"listbox\"\r\n [attr.aria-label]=\"'common.label.skin_tone' | translate\">\r\n @for (tone of tones; track tone) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__tone-option\"\r\n role=\"option\"\r\n [class.is-active]=\"effectiveTone() === tone\"\r\n [attr.aria-selected]=\"effectiveTone() === tone\"\r\n [flyTooltip]=\"toneLabelKey(tone) | translate\"\r\n (click)=\"onToneOptionClick(tone)\">\r\n {{ toneSwatch(tone) }}\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- ONE scrolling list; each category is a separator label over its own grid. Tabs wrapped to three\r\n rows in a sidebar drawer and cost a click to reach any group. -->\r\n <div\r\n class=\"fly-emoji-picker__list\"\r\n role=\"listbox\"\r\n [attr.id]=\"showTabs() ? 'fly-emoji-panel-emoji' : null\"\r\n [attr.aria-labelledby]=\"showTabs() ? 'fly-emoji-tab-emoji' : null\"\r\n [attr.aria-multiselectable]=\"selectionMode() === 'multi'\">\r\n @for (section of sections(); track section.id) {\r\n <div class=\"fly-emoji-picker__section\" role=\"group\" [attr.aria-label]=\"sectionLabel(section)\">\r\n <h4 class=\"fly-emoji-picker__heading\">{{ sectionLabel(section) }}</h4>\r\n <div class=\"fly-emoji-picker__grid\">\r\n @for (cell of section.cells; track cell.entry.id) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__cell\"\r\n role=\"option\"\r\n [class.is-active]=\"cell.selected\"\r\n [attr.aria-selected]=\"cell.selected\"\r\n [flyTooltip]=\"cell.entry.label\"\r\n [attr.aria-label]=\"cell.entry.label\"\r\n (click)=\"pick(cell.entry)\">\r\n @if (section.animated) {\r\n <!-- Hover lives on the animated element, not the button: a template reference\r\n declared inside a control-flow block isn't visible to the block's parent. -->\r\n <fly-animated-emoji\r\n #anim\r\n class=\"fly-emoji-picker__glyph\"\r\n [id]=\"cell.entry.id\"\r\n [size]=\"20\"\r\n (pointerenter)=\"onCellHover(anim)\"\r\n (pointerleave)=\"cancelHover()\" />\r\n } @else {\r\n <span class=\"fly-emoji-picker__glyph\">{{ cell.glyph }}</span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n } @empty {\r\n <p class=\"fly-emoji-picker__empty\">{{ 'agent.lookup.no_results' | translate }}</p>\r\n }\r\n\r\n <!-- Lives INSIDE the scroller, as the last row, rather than pinned below it \u2014 a static\r\n footer row permanently ate into the vertical space actual emoji rows could use;\r\n scrolled into view instead, it only costs space once you reach the end. -->\r\n @if (hasMore()) {\r\n <button type=\"button\" class=\"fly-emoji-picker__more\" (click)=\"toggleExpanded()\">\r\n {{ (isExpanded() ? 'common.label.emoji_show_less' : 'common.label.emoji_show_more') | translate }}\r\n </button>\r\n }\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block;block-size:100%;-webkit-backdrop-filter:blur(24px) saturate(130%);backdrop-filter:blur(24px) saturate(130%);--fly-emoji-cell: 36px;--fly-emoji-cell-gap: 6px;--fly-emoji-glyph: 17px;--fly-emoji-section-gap: 14px;--fly-emoji-bar-gap: 8px;--fly-emoji-search-font: 12px;--fly-emoji-search-padding: 8px 10px;--fly-emoji-heading-display: block}:host(.fly-emoji-picker--medium){--fly-emoji-cell: 30px;--fly-emoji-cell-gap: 5px;--fly-emoji-glyph: 16px;--fly-emoji-section-gap: 10px;--fly-emoji-bar-gap: 6px;--fly-emoji-search-font: 11.5px;--fly-emoji-search-padding: 6px 9px}:host(.fly-emoji-picker--small){--fly-emoji-cell: 24px;--fly-emoji-cell-gap: 3px;--fly-emoji-glyph: 15px;--fly-emoji-section-gap: 6px;--fly-emoji-bar-gap: 5px;--fly-emoji-search-font: 11px;--fly-emoji-search-padding: 4px 7px;--fly-emoji-heading-display: none;--fly-emoji-picker-list-max: 132px}.fly-emoji-picker{display:flex;flex-direction:column;gap:var(--fly-emoji-bar-gap);box-sizing:border-box;block-size:100%;color:var(--text-color, inherit)}.fly-emoji-picker__bar{display:flex;align-items:center;gap:10px}.fly-emoji-picker__search{flex:1 1 auto;min-inline-size:0;box-sizing:border-box;padding:var(--fly-emoji-search-padding);font:inherit;font-size:var(--fly-emoji-search-font);border:1px solid var(--surface-border, rgba(255, 255, 255, .12));border-radius:5px;background:transparent;color:var(--text-color, inherit);outline:none}.fly-emoji-picker__search:focus{border-color:var(--primary-color)}.fly-emoji-picker__list{display:flex;flex:1 1 auto;flex-direction:column;gap:var(--fly-emoji-section-gap);min-block-size:0;max-block-size:var(--fly-emoji-picker-list-max, 320px);overflow-y:auto;padding-inline-end:4px}.fly-emoji-picker__section{display:flex;flex-direction:column;gap:6px}.fly-emoji-picker__heading{display:var(--fly-emoji-heading-display);position:sticky;inset-block-start:0;z-index:1;margin:0;padding-block:3px;font-size:10px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:var(--text-color-secondary, rgba(235, 235, 245, .55));background:var(--surface-overlay, var(--surface-card, #1c1c1e))}.fly-emoji-picker__tones{position:relative;flex:0 0 auto}.fly-emoji-picker__tone-trigger{display:flex;align-items:center;gap:2px;inline-size:auto;block-size:22px;padding:0 4px;font-size:14px;line-height:1;border:1px solid var(--surface-border, rgba(255, 255, 255, .12));border-radius:5px;background:transparent;color:var(--text-color-secondary, rgba(235, 235, 245, .55));cursor:pointer;transition:background .1s ease,border-color .1s ease}.fly-emoji-picker__tone-trigger:hover{background:var(--surface-hover, rgba(255, 255, 255, .08))}.fly-emoji-picker__tone-trigger:focus-visible{outline:2px solid var(--primary-color);outline-offset:-1px}.fly-emoji-picker__tone-glyph{line-height:1}.fly-emoji-picker__tone-caret{font-size:8px}.fly-emoji-picker__tone-menu{position:absolute;inset-block-start:calc(100% + 4px);inset-inline-end:0;z-index:21;display:grid;grid-template-columns:repeat(3,1fr);gap:2px;padding:4px;background:var(--surface-overlay-strong, var(--surface-overlay, var(--surface-card, #1c1c1e)));border:1px solid var(--surface-border, rgba(255, 255, 255, .12));border-radius:8px;box-shadow:0 8px 24px #00000047}.fly-emoji-picker__tone-option{display:grid;place-items:center;inline-size:26px;block-size:26px;padding:0;font-size:15px;line-height:1;border:1px solid transparent;border-radius:5px;background:transparent;cursor:pointer;transition:background .1s ease,border-color .1s ease}.fly-emoji-picker__tone-option:hover{background:var(--surface-hover, rgba(255, 255, 255, .08))}.fly-emoji-picker__tone-option:focus-visible{outline:2px solid var(--primary-color);outline-offset:-1px}.fly-emoji-picker__tone-option.is-active{border-color:var(--primary-color);background:var(--surface-hover, rgba(255, 255, 255, .1))}.fly-emoji-picker__grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(var(--fly-emoji-cell),1fr));gap:var(--fly-emoji-cell-gap)}.fly-emoji-picker__cell{display:flex;align-items:center;justify-content:center;aspect-ratio:1;min-block-size:var(--fly-emoji-cell);padding:4px;border:1px solid transparent;border-radius:6px;background:transparent;color:var(--text-color, inherit);cursor:pointer;transition:background .1s ease,border-color .1s ease}.fly-emoji-picker__cell:hover{background:var(--surface-hover, rgba(255, 255, 255, .08));border-color:var(--surface-border, rgba(255, 255, 255, .12))}.fly-emoji-picker__cell:focus-visible{outline:2px solid var(--primary-color);outline-offset:-1px}.fly-emoji-picker__cell.is-active{border-color:var(--primary-color);background:var(--selection-bg, rgba(0, 113, 227, .24))}.fly-emoji-picker__glyph{direction:ltr;font-size:var(--fly-emoji-glyph);line-height:1}.fly-emoji-picker__more{align-self:flex-start;padding:3px 8px;font:inherit;font-size:11px;border:none;border-radius:999px;background:transparent;color:var(--primary-color);cursor:pointer}.fly-emoji-picker__more:hover{background:var(--surface-hover, rgba(255, 255, 255, .06))}.fly-emoji-picker__more:focus-visible{outline:2px solid var(--primary-color);outline-offset:1px}.fly-emoji-picker__empty{margin:0;padding:12px 4px;text-align:center;font-size:12px;color:var(--text-color-secondary, rgba(235, 235, 245, .5))}.fly-emoji-picker__tabs{display:flex;gap:4px;border-block-end:1px solid var(--surface-border, rgba(255, 255, 255, .12))}.fly-emoji-picker__tab{appearance:none;padding:6px 12px;font:inherit;font-size:12px;border:none;border-block-end:2px solid transparent;background:transparent;color:var(--text-color-secondary, rgba(235, 235, 245, .6));cursor:pointer}.fly-emoji-picker__tab.is-active{color:var(--text-color, inherit);border-block-end-color:var(--primary-color)}.fly-emoji-picker__tab:focus-visible{outline:2px solid var(--primary-color);outline-offset:-2px}.fly-emoji-picker__stickers{flex:1 1 auto;min-block-size:0;overflow-y:auto;display:grid;grid-template-columns:repeat(auto-fill,minmax(72px,1fr));gap:6px;padding:4px}.fly-emoji-picker__sticker{appearance:none;display:flex;align-items:center;justify-content:center;padding:4px;border:none;border-radius:10px;background:transparent;cursor:pointer}.fly-emoji-picker__sticker:hover,.fly-emoji-picker__sticker:focus-visible{background:var(--surface-hover, rgba(255, 255, 255, .08))}.fly-emoji-picker__sticker:focus-visible{outline:2px solid var(--primary-color);outline-offset:-2px}\n"], dependencies: [{ kind: "directive", type: FlyClickOutsideDirective, selector: "[flyClickOutside]", inputs: ["flyClickOutsideEnabled"], outputs: ["flyClickOutside"] }, { kind: "directive", type: FlyTooltipDirective, selector: "[flyTooltip], [data-tooltip]", inputs: ["flyTooltip", "flyTooltipPlacement", "flyTooltipDisabled", "flyTooltipDelay"] }, { kind: "component", type: FlyAnimatedEmojiComponent, selector: "fly-animated-emoji", inputs: ["id", "size", "autoplayOnFirstVisible", "playOnHover"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6923
7319
  }
6924
7320
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyEmojiPickerComponent, decorators: [{
6925
7321
  type: Component,
@@ -8745,11 +9141,11 @@ class FlyBlockUiComponent {
8745
9141
  return k && k.length > 0 ? k : 'common.loading';
8746
9142
  }, ...(ngDevMode ? [{ debugName: "resolvedMessageKey" }] : /* istanbul ignore next */ []));
8747
9143
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyBlockUiComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8748
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", 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 });
9144
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", 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()) {\r\n <div\r\n class=\"fly-block-ui\"\r\n role=\"status\"\r\n aria-live=\"polite\"\r\n aria-busy=\"true\"\r\n [attr.aria-label]=\"resolvedMessageKey() | translate\">\r\n <div class=\"fly-block-ui__card\">\r\n <i class=\"pi pi-spin pi-spinner fly-block-ui__spinner\" aria-hidden=\"true\"></i>\r\n <span class=\"fly-block-ui__text\">{{ resolvedMessageKey() | translate }}</span>\r\n </div>\r\n </div>\r\n}\r\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 });
8749
9145
  }
8750
9146
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyBlockUiComponent, decorators: [{
8751
9147
  type: Component,
8752
- 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"] }]
9148
+ args: [{ selector: 'fly-block-ui', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (active()) {\r\n <div\r\n class=\"fly-block-ui\"\r\n role=\"status\"\r\n aria-live=\"polite\"\r\n aria-busy=\"true\"\r\n [attr.aria-label]=\"resolvedMessageKey() | translate\">\r\n <div class=\"fly-block-ui__card\">\r\n <i class=\"pi pi-spin pi-spinner fly-block-ui__spinner\" aria-hidden=\"true\"></i>\r\n <span class=\"fly-block-ui__text\">{{ resolvedMessageKey() | translate }}</span>\r\n </div>\r\n </div>\r\n}\r\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"] }]
8753
9149
  }], propDecorators: { active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: true }] }], messageKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "messageKey", required: false }] }] } });
8754
9150
 
8755
9151
  /**
@@ -9528,11 +9924,11 @@ class FlyDrawerComponent {
9528
9924
  }
9529
9925
  }
9530
9926
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyDrawerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9531
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyDrawerComponent, isStandalone: true, selector: "fly-drawer", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null }, side: { classPropertyName: "side", publicName: "side", isSignal: true, isRequired: false, transformFunction: null }, dismissOnScrim: { classPropertyName: "dismissOnScrim", publicName: "dismissOnScrim", isSignal: true, isRequired: false, transformFunction: null }, dismissOnEscape: { classPropertyName: "dismissOnEscape", publicName: "dismissOnEscape", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, hideCloseButton: { classPropertyName: "hideCloseButton", publicName: "hideCloseButton", isSignal: true, isRequired: false, transformFunction: null }, bodyPadding: { classPropertyName: "bodyPadding", publicName: "bodyPadding", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", openChange: "openChange" }, host: { listeners: { "document:keydown.escape": "onEscape()" }, properties: { "class.fly-drawer--fixed": "position() === 'fixed'" } }, ngImport: i0, template: "<!--\r\n Windowed overlay drawer. Renders only while mounted (open or sliding out).\r\n Scrim + panel are absolutely positioned within the nearest positioned\r\n ancestor (the consuming app root must be position:relative).\r\n-->\r\n@if (rendered()) {\r\n <div\r\n class=\"fly-drawer__scrim\"\r\n [class.fly-drawer__scrim--leaving]=\"leaving()\"\r\n (click)=\"onScrimClick()\"\r\n aria-hidden=\"true\"\r\n ></div>\r\n\r\n <div\r\n class=\"fly-drawer__panel\"\r\n [ngClass]=\"panelClass()\"\r\n [class.fly-drawer__panel--leaving]=\"leaving()\"\r\n [style.--fly-drawer-width]=\"width()\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n [attr.aria-labelledby]=\"labelledBy()\"\r\n [attr.aria-label]=\"!labelledBy() && ariaLabel() ? (ariaLabel()! | translate) : null\"\r\n cdkTrapFocus\r\n (animationend)=\"onPanelAnimationEnd($event.animationName)\"\r\n >\r\n <!-- Header: custom slot, else heading + close \u2715. -->\r\n <ng-content select=\"[flyDrawerHeader]\">\r\n @if (heading()) {\r\n <header class=\"fly-drawer__header\">\r\n <h2 class=\"fly-drawer__title\" [id]=\"headingId\">{{ heading()! | translate }}</h2>\r\n @if (!hideCloseButton()) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-drawer__close\"\r\n (click)=\"requestClose()\"\r\n [attr.aria-label]=\"'common.action.close' | translate\"\r\n >\r\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n </header>\r\n }\r\n </ng-content>\r\n\r\n <div class=\"fly-drawer__body\" [ngClass]=\"'fly-drawer__body--pad-' + bodyPadding()\">\r\n <ng-content></ng-content>\r\n </div>\r\n\r\n <ng-content select=\"[flyDrawerFooter]\"></ng-content>\r\n </div>\r\n}\r\n", styles: [":host{position:absolute;inset:0;z-index:60;pointer-events:none}:host(.fly-drawer--fixed){position:fixed;block-size:100dvh;z-index:var(--z-overlay, 100)}.fly-drawer__scrim{position:absolute;inset:0;pointer-events:auto;background:#00000061;opacity:1;animation:fly-drawer-scrim-in .24s ease both}.fly-drawer__scrim--leaving{animation-name:fly-drawer-scrim-out;animation-duration:.2s}.fly-drawer__panel{--fly-drawer-offset: 100%;position:absolute;inset-block:0;inset-inline-end:0;pointer-events:auto;display:flex;flex-direction:column;min-block-size:0;inline-size:var(--fly-drawer-width, min(480px, 100%));max-inline-size:100%;background:var(--surface-overlay, #fff);-webkit-backdrop-filter:blur(30px) saturate(150%);backdrop-filter:blur(30px) saturate(150%);border-inline-start:1px solid var(--surface-border, rgba(0, 0, 0, .08));box-shadow:-16px 0 48px #00000038;animation:fly-drawer-in .28s cubic-bezier(.32,.72,0,1) both;will-change:transform}.fly-drawer__panel--sm{inline-size:var(--fly-drawer-width, min(360px, 100%))}.fly-drawer__panel--md{inline-size:var(--fly-drawer-width, min(480px, 100%))}.fly-drawer__panel--lg{inline-size:var(--fly-drawer-width, min(640px, 100%))}.fly-drawer__panel--xl{inline-size:var(--fly-drawer-width, min(960px, 94%))}.fly-drawer__panel--side-start{--fly-drawer-offset: -100%;inset-inline:0 auto;border-inline-start:none;border-inline-end:1px solid var(--surface-border, rgba(0, 0, 0, .08));box-shadow:16px 0 48px #00000038}.fly-drawer__panel--leaving{animation-name:fly-drawer-out;animation-duration:.24s;animation-timing-function:cubic-bezier(.4,0,1,1)}@keyframes fly-drawer-in{0%{transform:translate(var(--fly-drawer-offset))}to{transform:translate(0)}}@keyframes fly-drawer-out{0%{transform:translate(0)}to{transform:translate(var(--fly-drawer-offset))}}@keyframes fly-drawer-scrim-in{0%{opacity:0}to{opacity:1}}@keyframes fly-drawer-scrim-out{0%{opacity:1}to{opacity:0}}:host-context([dir=rtl]) .fly-drawer__panel{--fly-drawer-offset: -100%;box-shadow:16px 0 48px #00000038;border-inline-start:1px solid var(--surface-border, rgba(0, 0, 0, .08))}:host-context([dir=rtl]) .fly-drawer__panel--side-start{--fly-drawer-offset: 100%}.fly-drawer__header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 18px;border-block-end:1px solid var(--surface-border, rgba(0, 0, 0, .08));flex:0 0 auto}.fly-drawer__title{margin:0;font-size:16px;font-weight:600;color:var(--label-primary, #1d1d1f)}.fly-drawer__close{display:inline-flex;align-items:center;justify-content:center;inline-size:30px;block-size:30px;border:none;border-radius:8px;background:transparent;color:var(--label-secondary, #6e6e73);cursor:pointer}.fly-drawer__close:hover{background:#0000000f}.fly-drawer__body{flex:1 1 auto;min-block-size:0;overflow-y:auto;padding:var(--fly-drawer-body-padding, 16px 18px)}.fly-drawer__body--pad-none{--fly-drawer-body-padding: 0}.fly-drawer__body--pad-sm{--fly-drawer-body-padding: 12px 14px}.fly-drawer__body--pad-md{--fly-drawer-body-padding: 16px 18px}.fly-drawer__body--pad-lg{--fly-drawer-body-padding: 20px 22px}:host ::ng-deep [flyDrawerFooter]{flex:0 0 auto;display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:12px 18px;border-block-start:1px solid var(--surface-border, rgba(0, 0, 0, .08))}:host-context(html.dark-theme) .fly-drawer__close:hover{background:#ffffff14}@media(prefers-reduced-motion:reduce){.fly-drawer__scrim,.fly-drawer__panel,.fly-drawer__scrim--leaving,.fly-drawer__panel--leaving{animation:none}}@media(forced-colors:active){.fly-drawer__panel{border:1px solid CanvasText}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i2.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9927
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyDrawerComponent, isStandalone: true, selector: "fly-drawer", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null }, side: { classPropertyName: "side", publicName: "side", isSignal: true, isRequired: false, transformFunction: null }, dismissOnScrim: { classPropertyName: "dismissOnScrim", publicName: "dismissOnScrim", isSignal: true, isRequired: false, transformFunction: null }, dismissOnEscape: { classPropertyName: "dismissOnEscape", publicName: "dismissOnEscape", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, hideCloseButton: { classPropertyName: "hideCloseButton", publicName: "hideCloseButton", isSignal: true, isRequired: false, transformFunction: null }, bodyPadding: { classPropertyName: "bodyPadding", publicName: "bodyPadding", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", openChange: "openChange" }, host: { listeners: { "document:keydown.escape": "onEscape()" }, properties: { "class.fly-drawer--fixed": "position() === 'fixed'" } }, ngImport: i0, template: "<!--\r\n Windowed overlay drawer. Renders only while mounted (open or sliding out).\r\n Scrim + panel are absolutely positioned within the nearest positioned\r\n ancestor (the consuming app root must be position:relative).\r\n-->\r\n@if (rendered()) {\r\n <div\r\n class=\"fly-drawer__scrim\"\r\n [class.fly-drawer__scrim--leaving]=\"leaving()\"\r\n (click)=\"onScrimClick()\"\r\n aria-hidden=\"true\"\r\n ></div>\r\n\r\n <div\r\n class=\"fly-drawer__panel\"\r\n [ngClass]=\"panelClass()\"\r\n [class.fly-drawer__panel--leaving]=\"leaving()\"\r\n [style.--fly-drawer-width]=\"width()\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n [attr.aria-labelledby]=\"labelledBy()\"\r\n [attr.aria-label]=\"!labelledBy() && ariaLabel() ? (ariaLabel()! | translate) : null\"\r\n cdkTrapFocus\r\n (animationend)=\"onPanelAnimationEnd($event.animationName)\"\r\n >\r\n <!-- Header: custom slot, else heading + close \u2715. -->\r\n <ng-content select=\"[flyDrawerHeader]\">\r\n @if (heading()) {\r\n <header class=\"fly-drawer__header\">\r\n <h2 class=\"fly-drawer__title\" [id]=\"headingId\">{{ heading()! | translate }}</h2>\r\n @if (!hideCloseButton()) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-drawer__close\"\r\n (click)=\"requestClose()\"\r\n [attr.aria-label]=\"'common.action.close' | translate\"\r\n >\r\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n </header>\r\n }\r\n </ng-content>\r\n\r\n <div class=\"fly-drawer__body\" [ngClass]=\"'fly-drawer__body--pad-' + bodyPadding()\">\r\n <ng-content></ng-content>\r\n </div>\r\n\r\n <ng-content select=\"[flyDrawerFooter]\"></ng-content>\r\n </div>\r\n}\r\n", styles: [":host{position:absolute;inset:0;z-index:60;pointer-events:none}:host(.fly-drawer--fixed){position:fixed;block-size:100dvh;z-index:var(--z-overlay, 100)}.fly-drawer__scrim{position:absolute;inset:0;pointer-events:auto;background:#00000061;opacity:1;animation:fly-drawer-scrim-in .24s ease both}.fly-drawer__scrim--leaving{animation-name:fly-drawer-scrim-out;animation-duration:.2s}.fly-drawer__panel{--fly-drawer-offset: 100%;position:absolute;inset-block:0;inset-inline-end:0;pointer-events:auto;display:flex;flex-direction:column;min-block-size:0;inline-size:var(--fly-drawer-width, min(480px, 100%));max-inline-size:100%;background:var(--surface-overlay, #fff);-webkit-backdrop-filter:blur(30px) saturate(150%);backdrop-filter:blur(30px) saturate(150%);border-inline-start:1px solid var(--surface-border, rgba(0, 0, 0, .08));box-shadow:-16px 0 48px #00000038;animation:fly-drawer-in .28s cubic-bezier(.32,.72,0,1) both;will-change:transform}.fly-drawer__panel--sm{inline-size:var(--fly-drawer-width, min(360px, 100%))}.fly-drawer__panel--md{inline-size:var(--fly-drawer-width, min(480px, 100%))}.fly-drawer__panel--lg{inline-size:var(--fly-drawer-width, min(640px, 100%))}.fly-drawer__panel--xl{inline-size:var(--fly-drawer-width, min(960px, 94%))}.fly-drawer__panel--side-start{--fly-drawer-offset: -100%;inset-inline:0 auto;border-inline-start:none;border-inline-end:1px solid var(--surface-border, rgba(0, 0, 0, .08));box-shadow:16px 0 48px #00000038}.fly-drawer__panel--leaving{animation-name:fly-drawer-out;animation-duration:.24s;animation-timing-function:cubic-bezier(.4,0,1,1)}@keyframes fly-drawer-in{0%{transform:translate(var(--fly-drawer-offset))}to{transform:translate(0)}}@keyframes fly-drawer-out{0%{transform:translate(0)}to{transform:translate(var(--fly-drawer-offset))}}@keyframes fly-drawer-scrim-in{0%{opacity:0}to{opacity:1}}@keyframes fly-drawer-scrim-out{0%{opacity:1}to{opacity:0}}:host-context([dir=rtl]) .fly-drawer__panel{--fly-drawer-offset: -100%;box-shadow:16px 0 48px #00000038;border-inline-start:1px solid var(--surface-border, rgba(0, 0, 0, .08))}:host-context([dir=rtl]) .fly-drawer__panel--side-start{--fly-drawer-offset: 100%}.fly-drawer__header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 18px;border-block-end:1px solid var(--surface-border, rgba(0, 0, 0, .08));flex:0 0 auto}.fly-drawer__title{margin:0;font-size:16px;font-weight:600;color:var(--label-primary, #1d1d1f)}.fly-drawer__close{display:inline-flex;align-items:center;justify-content:center;inline-size:30px;block-size:30px;border:none;border-radius:8px;background:transparent;color:var(--label-secondary, #6e6e73);cursor:pointer}.fly-drawer__close:hover{background:#0000000f}.fly-drawer__body{flex:1 1 auto;min-block-size:0;overflow-y:auto;padding:var(--fly-drawer-body-padding, 16px 18px);container-type:inline-size}.fly-drawer__body--pad-none{--fly-drawer-body-padding: 0}.fly-drawer__body--pad-sm{--fly-drawer-body-padding: 12px 14px}.fly-drawer__body--pad-md{--fly-drawer-body-padding: 16px 18px}.fly-drawer__body--pad-lg{--fly-drawer-body-padding: 20px 22px}:host ::ng-deep [flyDrawerFooter]{flex:0 0 auto;display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:12px 18px;border-block-start:1px solid var(--surface-border, rgba(0, 0, 0, .08))}:host-context(html.dark-theme) .fly-drawer__close:hover{background:#ffffff14}@media(prefers-reduced-motion:reduce){.fly-drawer__scrim,.fly-drawer__panel,.fly-drawer__scrim--leaving,.fly-drawer__panel--leaving{animation:none}}@media(forced-colors:active){.fly-drawer__panel{border:1px solid CanvasText}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i2.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9532
9928
  }
9533
9929
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyDrawerComponent, decorators: [{
9534
9930
  type: Component,
9535
- args: [{ selector: 'fly-drawer', standalone: true, imports: [CommonModule, A11yModule, TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, host: { '[class.fly-drawer--fixed]': "position() === 'fixed'" }, template: "<!--\r\n Windowed overlay drawer. Renders only while mounted (open or sliding out).\r\n Scrim + panel are absolutely positioned within the nearest positioned\r\n ancestor (the consuming app root must be position:relative).\r\n-->\r\n@if (rendered()) {\r\n <div\r\n class=\"fly-drawer__scrim\"\r\n [class.fly-drawer__scrim--leaving]=\"leaving()\"\r\n (click)=\"onScrimClick()\"\r\n aria-hidden=\"true\"\r\n ></div>\r\n\r\n <div\r\n class=\"fly-drawer__panel\"\r\n [ngClass]=\"panelClass()\"\r\n [class.fly-drawer__panel--leaving]=\"leaving()\"\r\n [style.--fly-drawer-width]=\"width()\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n [attr.aria-labelledby]=\"labelledBy()\"\r\n [attr.aria-label]=\"!labelledBy() && ariaLabel() ? (ariaLabel()! | translate) : null\"\r\n cdkTrapFocus\r\n (animationend)=\"onPanelAnimationEnd($event.animationName)\"\r\n >\r\n <!-- Header: custom slot, else heading + close \u2715. -->\r\n <ng-content select=\"[flyDrawerHeader]\">\r\n @if (heading()) {\r\n <header class=\"fly-drawer__header\">\r\n <h2 class=\"fly-drawer__title\" [id]=\"headingId\">{{ heading()! | translate }}</h2>\r\n @if (!hideCloseButton()) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-drawer__close\"\r\n (click)=\"requestClose()\"\r\n [attr.aria-label]=\"'common.action.close' | translate\"\r\n >\r\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n </header>\r\n }\r\n </ng-content>\r\n\r\n <div class=\"fly-drawer__body\" [ngClass]=\"'fly-drawer__body--pad-' + bodyPadding()\">\r\n <ng-content></ng-content>\r\n </div>\r\n\r\n <ng-content select=\"[flyDrawerFooter]\"></ng-content>\r\n </div>\r\n}\r\n", styles: [":host{position:absolute;inset:0;z-index:60;pointer-events:none}:host(.fly-drawer--fixed){position:fixed;block-size:100dvh;z-index:var(--z-overlay, 100)}.fly-drawer__scrim{position:absolute;inset:0;pointer-events:auto;background:#00000061;opacity:1;animation:fly-drawer-scrim-in .24s ease both}.fly-drawer__scrim--leaving{animation-name:fly-drawer-scrim-out;animation-duration:.2s}.fly-drawer__panel{--fly-drawer-offset: 100%;position:absolute;inset-block:0;inset-inline-end:0;pointer-events:auto;display:flex;flex-direction:column;min-block-size:0;inline-size:var(--fly-drawer-width, min(480px, 100%));max-inline-size:100%;background:var(--surface-overlay, #fff);-webkit-backdrop-filter:blur(30px) saturate(150%);backdrop-filter:blur(30px) saturate(150%);border-inline-start:1px solid var(--surface-border, rgba(0, 0, 0, .08));box-shadow:-16px 0 48px #00000038;animation:fly-drawer-in .28s cubic-bezier(.32,.72,0,1) both;will-change:transform}.fly-drawer__panel--sm{inline-size:var(--fly-drawer-width, min(360px, 100%))}.fly-drawer__panel--md{inline-size:var(--fly-drawer-width, min(480px, 100%))}.fly-drawer__panel--lg{inline-size:var(--fly-drawer-width, min(640px, 100%))}.fly-drawer__panel--xl{inline-size:var(--fly-drawer-width, min(960px, 94%))}.fly-drawer__panel--side-start{--fly-drawer-offset: -100%;inset-inline:0 auto;border-inline-start:none;border-inline-end:1px solid var(--surface-border, rgba(0, 0, 0, .08));box-shadow:16px 0 48px #00000038}.fly-drawer__panel--leaving{animation-name:fly-drawer-out;animation-duration:.24s;animation-timing-function:cubic-bezier(.4,0,1,1)}@keyframes fly-drawer-in{0%{transform:translate(var(--fly-drawer-offset))}to{transform:translate(0)}}@keyframes fly-drawer-out{0%{transform:translate(0)}to{transform:translate(var(--fly-drawer-offset))}}@keyframes fly-drawer-scrim-in{0%{opacity:0}to{opacity:1}}@keyframes fly-drawer-scrim-out{0%{opacity:1}to{opacity:0}}:host-context([dir=rtl]) .fly-drawer__panel{--fly-drawer-offset: -100%;box-shadow:16px 0 48px #00000038;border-inline-start:1px solid var(--surface-border, rgba(0, 0, 0, .08))}:host-context([dir=rtl]) .fly-drawer__panel--side-start{--fly-drawer-offset: 100%}.fly-drawer__header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 18px;border-block-end:1px solid var(--surface-border, rgba(0, 0, 0, .08));flex:0 0 auto}.fly-drawer__title{margin:0;font-size:16px;font-weight:600;color:var(--label-primary, #1d1d1f)}.fly-drawer__close{display:inline-flex;align-items:center;justify-content:center;inline-size:30px;block-size:30px;border:none;border-radius:8px;background:transparent;color:var(--label-secondary, #6e6e73);cursor:pointer}.fly-drawer__close:hover{background:#0000000f}.fly-drawer__body{flex:1 1 auto;min-block-size:0;overflow-y:auto;padding:var(--fly-drawer-body-padding, 16px 18px)}.fly-drawer__body--pad-none{--fly-drawer-body-padding: 0}.fly-drawer__body--pad-sm{--fly-drawer-body-padding: 12px 14px}.fly-drawer__body--pad-md{--fly-drawer-body-padding: 16px 18px}.fly-drawer__body--pad-lg{--fly-drawer-body-padding: 20px 22px}:host ::ng-deep [flyDrawerFooter]{flex:0 0 auto;display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:12px 18px;border-block-start:1px solid var(--surface-border, rgba(0, 0, 0, .08))}:host-context(html.dark-theme) .fly-drawer__close:hover{background:#ffffff14}@media(prefers-reduced-motion:reduce){.fly-drawer__scrim,.fly-drawer__panel,.fly-drawer__scrim--leaving,.fly-drawer__panel--leaving{animation:none}}@media(forced-colors:active){.fly-drawer__panel{border:1px solid CanvasText}}\n"] }]
9931
+ args: [{ selector: 'fly-drawer', standalone: true, imports: [CommonModule, A11yModule, TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, host: { '[class.fly-drawer--fixed]': "position() === 'fixed'" }, template: "<!--\r\n Windowed overlay drawer. Renders only while mounted (open or sliding out).\r\n Scrim + panel are absolutely positioned within the nearest positioned\r\n ancestor (the consuming app root must be position:relative).\r\n-->\r\n@if (rendered()) {\r\n <div\r\n class=\"fly-drawer__scrim\"\r\n [class.fly-drawer__scrim--leaving]=\"leaving()\"\r\n (click)=\"onScrimClick()\"\r\n aria-hidden=\"true\"\r\n ></div>\r\n\r\n <div\r\n class=\"fly-drawer__panel\"\r\n [ngClass]=\"panelClass()\"\r\n [class.fly-drawer__panel--leaving]=\"leaving()\"\r\n [style.--fly-drawer-width]=\"width()\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n [attr.aria-labelledby]=\"labelledBy()\"\r\n [attr.aria-label]=\"!labelledBy() && ariaLabel() ? (ariaLabel()! | translate) : null\"\r\n cdkTrapFocus\r\n (animationend)=\"onPanelAnimationEnd($event.animationName)\"\r\n >\r\n <!-- Header: custom slot, else heading + close \u2715. -->\r\n <ng-content select=\"[flyDrawerHeader]\">\r\n @if (heading()) {\r\n <header class=\"fly-drawer__header\">\r\n <h2 class=\"fly-drawer__title\" [id]=\"headingId\">{{ heading()! | translate }}</h2>\r\n @if (!hideCloseButton()) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-drawer__close\"\r\n (click)=\"requestClose()\"\r\n [attr.aria-label]=\"'common.action.close' | translate\"\r\n >\r\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n </header>\r\n }\r\n </ng-content>\r\n\r\n <div class=\"fly-drawer__body\" [ngClass]=\"'fly-drawer__body--pad-' + bodyPadding()\">\r\n <ng-content></ng-content>\r\n </div>\r\n\r\n <ng-content select=\"[flyDrawerFooter]\"></ng-content>\r\n </div>\r\n}\r\n", styles: [":host{position:absolute;inset:0;z-index:60;pointer-events:none}:host(.fly-drawer--fixed){position:fixed;block-size:100dvh;z-index:var(--z-overlay, 100)}.fly-drawer__scrim{position:absolute;inset:0;pointer-events:auto;background:#00000061;opacity:1;animation:fly-drawer-scrim-in .24s ease both}.fly-drawer__scrim--leaving{animation-name:fly-drawer-scrim-out;animation-duration:.2s}.fly-drawer__panel{--fly-drawer-offset: 100%;position:absolute;inset-block:0;inset-inline-end:0;pointer-events:auto;display:flex;flex-direction:column;min-block-size:0;inline-size:var(--fly-drawer-width, min(480px, 100%));max-inline-size:100%;background:var(--surface-overlay, #fff);-webkit-backdrop-filter:blur(30px) saturate(150%);backdrop-filter:blur(30px) saturate(150%);border-inline-start:1px solid var(--surface-border, rgba(0, 0, 0, .08));box-shadow:-16px 0 48px #00000038;animation:fly-drawer-in .28s cubic-bezier(.32,.72,0,1) both;will-change:transform}.fly-drawer__panel--sm{inline-size:var(--fly-drawer-width, min(360px, 100%))}.fly-drawer__panel--md{inline-size:var(--fly-drawer-width, min(480px, 100%))}.fly-drawer__panel--lg{inline-size:var(--fly-drawer-width, min(640px, 100%))}.fly-drawer__panel--xl{inline-size:var(--fly-drawer-width, min(960px, 94%))}.fly-drawer__panel--side-start{--fly-drawer-offset: -100%;inset-inline:0 auto;border-inline-start:none;border-inline-end:1px solid var(--surface-border, rgba(0, 0, 0, .08));box-shadow:16px 0 48px #00000038}.fly-drawer__panel--leaving{animation-name:fly-drawer-out;animation-duration:.24s;animation-timing-function:cubic-bezier(.4,0,1,1)}@keyframes fly-drawer-in{0%{transform:translate(var(--fly-drawer-offset))}to{transform:translate(0)}}@keyframes fly-drawer-out{0%{transform:translate(0)}to{transform:translate(var(--fly-drawer-offset))}}@keyframes fly-drawer-scrim-in{0%{opacity:0}to{opacity:1}}@keyframes fly-drawer-scrim-out{0%{opacity:1}to{opacity:0}}:host-context([dir=rtl]) .fly-drawer__panel{--fly-drawer-offset: -100%;box-shadow:16px 0 48px #00000038;border-inline-start:1px solid var(--surface-border, rgba(0, 0, 0, .08))}:host-context([dir=rtl]) .fly-drawer__panel--side-start{--fly-drawer-offset: 100%}.fly-drawer__header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 18px;border-block-end:1px solid var(--surface-border, rgba(0, 0, 0, .08));flex:0 0 auto}.fly-drawer__title{margin:0;font-size:16px;font-weight:600;color:var(--label-primary, #1d1d1f)}.fly-drawer__close{display:inline-flex;align-items:center;justify-content:center;inline-size:30px;block-size:30px;border:none;border-radius:8px;background:transparent;color:var(--label-secondary, #6e6e73);cursor:pointer}.fly-drawer__close:hover{background:#0000000f}.fly-drawer__body{flex:1 1 auto;min-block-size:0;overflow-y:auto;padding:var(--fly-drawer-body-padding, 16px 18px);container-type:inline-size}.fly-drawer__body--pad-none{--fly-drawer-body-padding: 0}.fly-drawer__body--pad-sm{--fly-drawer-body-padding: 12px 14px}.fly-drawer__body--pad-md{--fly-drawer-body-padding: 16px 18px}.fly-drawer__body--pad-lg{--fly-drawer-body-padding: 20px 22px}:host ::ng-deep [flyDrawerFooter]{flex:0 0 auto;display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:12px 18px;border-block-start:1px solid var(--surface-border, rgba(0, 0, 0, .08))}:host-context(html.dark-theme) .fly-drawer__close:hover{background:#ffffff14}@media(prefers-reduced-motion:reduce){.fly-drawer__scrim,.fly-drawer__panel,.fly-drawer__scrim--leaving,.fly-drawer__panel--leaving{animation:none}}@media(forced-colors:active){.fly-drawer__panel{border:1px solid CanvasText}}\n"] }]
9536
9932
  }], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }], side: [{ type: i0.Input, args: [{ isSignal: true, alias: "side", required: false }] }], dismissOnScrim: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissOnScrim", required: false }] }], dismissOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissOnEscape", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], hideCloseButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideCloseButton", required: false }] }], bodyPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "bodyPadding", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], openChange: [{ type: i0.Output, args: ["openChange"] }], onEscape: [{
9537
9933
  type: HostListener,
9538
9934
  args: ['document:keydown.escape']
@@ -12894,6 +13290,13 @@ const ROOT_DRAFT_KEY = '__root__';
12894
13290
  * eager load-all) — the explicit fix for the legacy unpaged-children scale bug
12895
13291
  * — and each level pages independently via `loadMore`.
12896
13292
  *
13293
+ * The `comments` core app owns no user profile data, so `FlyComment` carries
13294
+ * `authorUserId` and an OPTIONAL `authorDisplayName`. Rather than make every host
13295
+ * map names in itself (and render GUIDs when it forgets), bind
13296
+ * `[resolveDisplayName]` to `FlyUserDirectoryService.label` — see
13297
+ * {@link FlyCommentThreadComponent.resolveDisplayName}. Still transport-agnostic:
13298
+ * the resolver is a function the host hands over, not a call this component makes.
13299
+ *
12897
13300
  * i18n is self-sufficient through the `comment.*` keys in `DS_BASELINE_LOCALES`
12898
13301
  * (en/ar/fr/ur), overridable by any consumer key of the same name. RTL works via
12899
13302
  * logical CSS. Styling uses DS theme tokens (`--surface-*`, `--label-*`,
@@ -12906,6 +13309,7 @@ const ROOT_DRAFT_KEY = '__root__';
12906
13309
  * [rootPage]="rootPage()"
12907
13310
  * [repliesByParent]="repliesByParent()"
12908
13311
  * [currentUserId]="me.id"
13312
+ * [resolveDisplayName]="directory.label"
12909
13313
  * [canModerateThread]="canModerate()"
12910
13314
  * [isLocked]="thread().isLocked"
12911
13315
  * (loadReplies)="onLoadReplies($event)"
@@ -12942,6 +13346,23 @@ class FlyCommentThreadComponent {
12942
13346
  /** Thread-scoped moderator permission (lock/unlock). Additive — distinct from the
12943
13347
  * per-comment `canModerate` flag, which gates delete-any on that specific comment. */
12944
13348
  canModerateThread = input(false, ...(ngDevMode ? [{ debugName: "canModerateThread" }] : /* istanbul ignore next */ []));
13349
+ /**
13350
+ * Optional id→name lookup, for the common case where the backend DTO carries only
13351
+ * `authorUserId`. The `comments` core app owns no user profile data, so without
13352
+ * this every host had to map display names into `authorDisplayName` itself — and
13353
+ * a host that forgot rendered raw GUIDs at the reader.
13354
+ *
13355
+ * Bind `FlyUserDirectoryService.label` (a signal-backed read-through cache that
13356
+ * resolves on demand and batches). Because the component reads it during render,
13357
+ * the name appears by itself once the lookup lands — the host keeps a plain
13358
+ * page signal, with no `computed` derivation and no priming.
13359
+ *
13360
+ * The component still never calls an API: this is a function the HOST supplies,
13361
+ * exactly like `fly-entity-lookup`'s `LOOKUP_APP_NAME_RESOLVER`. Absent (or
13362
+ * returning `null`), rendering falls back as before. See {@link authorLabel} for
13363
+ * the resolution order.
13364
+ */
13365
+ resolveDisplayName = input(null, ...(ngDevMode ? [{ debugName: "resolveDisplayName" }] : /* istanbul ignore next */ []));
12945
13366
  // ── Outputs ──────────────────────────────────────────────────────────────────
12946
13367
  /** Expand a collapsed reply subtree — always page 1 of that parent. */
12947
13368
  loadReplies = output();
@@ -13002,6 +13423,25 @@ class FlyCommentThreadComponent {
13002
13423
  const me = this.currentUserId();
13003
13424
  return !!me && me === comment.authorUserId;
13004
13425
  }
13426
+ /**
13427
+ * The name to render for a comment's author. Resolution order:
13428
+ * explicit `authorDisplayName` (a host that already knows the name, or a
13429
+ * backend that returns one, always wins) → {@link resolveDisplayName} →
13430
+ * the raw `authorUserId`.
13431
+ *
13432
+ * The raw id remains the last resort rather than a placeholder: it is stable,
13433
+ * unique, and lets a reader correlate two comments by the same author even
13434
+ * when the directory is unreachable.
13435
+ */
13436
+ authorLabel(comment) {
13437
+ if (comment.authorDisplayName)
13438
+ return comment.authorDisplayName;
13439
+ return this.resolveDisplayName()?.(comment.authorUserId) || comment.authorUserId;
13440
+ }
13441
+ /** First character of {@link authorLabel}, for the avatar-initials fallback. */
13442
+ authorInitial(comment) {
13443
+ return this.authorLabel(comment).charAt(0);
13444
+ }
13005
13445
  canReport(comment) {
13006
13446
  return !this.isReadonly() && !comment.isDeleted && !this.isOwn(comment) && !!this.currentUserId();
13007
13447
  }
@@ -13172,16 +13612,16 @@ class FlyCommentThreadComponent {
13172
13612
  this._drafts.set(map);
13173
13613
  }
13174
13614
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyCommentThreadComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
13175
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyCommentThreadComponent, isStandalone: true, selector: "fly-comment-thread", inputs: { rootPage: { classPropertyName: "rootPage", publicName: "rootPage", isSignal: true, isRequired: true, transformFunction: null }, repliesByParent: { classPropertyName: "repliesByParent", publicName: "repliesByParent", isSignal: true, isRequired: false, transformFunction: null }, sort: { classPropertyName: "sort", publicName: "sort", isSignal: true, isRequired: false, transformFunction: null }, isLocked: { classPropertyName: "isLocked", publicName: "isLocked", isSignal: true, isRequired: false, transformFunction: null }, currentUserId: { classPropertyName: "currentUserId", publicName: "currentUserId", isSignal: true, isRequired: false, transformFunction: null }, isReadonly: { classPropertyName: "isReadonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, canModerateThread: { classPropertyName: "canModerateThread", publicName: "canModerateThread", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { loadReplies: "loadReplies", loadMore: "loadMore", submit: "submit", edit: "edit", delete: "delete", report: "report", lockToggle: "lockToggle", sortChange: "sortChange" }, host: { properties: { "class.fly-comment-thread--locked": "isLocked()", "class.fly-comment-thread--readonly": "isReadonly()" }, classAttribute: "fly-comment-thread" }, ngImport: i0, template: "<div class=\"fly-comment-thread__body\">\r\n <!-- \u2500\u2500 Header: count + sort + lock toggle \u2500\u2500 -->\r\n <header class=\"fly-comment-thread__header\">\r\n <span class=\"fly-comment-thread__count\">\r\n {{ 'comment.count' | translate: { n: rootPage().total } }}\r\n </span>\r\n\r\n <div class=\"fly-comment-thread__header-actions\">\r\n <div class=\"fly-comment-thread__sort\" role=\"group\" [attr.aria-label]=\"'comment.sort_label' | translate\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__sort-btn\"\r\n [class.fly-comment-thread__sort-btn--active]=\"sort() === 'newest'\"\r\n (click)=\"onSortPick('newest')\">\r\n {{ 'comment.sort_newest' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__sort-btn\"\r\n [class.fly-comment-thread__sort-btn--active]=\"sort() === 'oldest'\"\r\n (click)=\"onSortPick('oldest')\">\r\n {{ 'comment.sort_oldest' | translate }}\r\n </button>\r\n </div>\r\n\r\n @if (canModerateThread() && !isReadonly()) {\r\n <button type=\"button\" class=\"fly-comment-thread__lock-btn\" (click)=\"toggleLock()\">\r\n {{ (isLocked() ? 'comment.unlock' : 'comment.lock') | translate }}\r\n </button>\r\n }\r\n </div>\r\n </header>\r\n\r\n @if (isLocked()) {\r\n <p class=\"fly-comment-thread__locked-notice\">{{ 'comment.locked_notice' | translate }}</p>\r\n }\r\n\r\n <!-- \u2500\u2500 Root composer \u2500\u2500 -->\r\n @if (canReply()) {\r\n <div class=\"fly-comment-thread__composer\">\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"3\"\r\n [id]=\"fieldId('root-composer')\"\r\n [attr.aria-label]=\"'comment.composer_placeholder' | translate\"\r\n [placeholder]=\"'comment.composer_placeholder' | translate\"\r\n [ngModel]=\"rootDraft()\"\r\n (ngModelChange)=\"onRootDraftChange($event)\"></textarea>\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__submit-btn\"\r\n [disabled]=\"!rootDraft().trim()\"\r\n (click)=\"submitRoot()\">\r\n {{ 'comment.post' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- \u2500\u2500 Root comments \u2500\u2500 -->\r\n @if (rootPage().items.length === 0) {\r\n <p class=\"fly-comment-thread__empty\">{{ 'comment.empty' | translate }}</p>\r\n } @else {\r\n <ul class=\"fly-comment-thread__list\" role=\"list\">\r\n @for (c of rootPage().items; track c.id) {\r\n <ng-container [ngTemplateOutlet]=\"commentNode\" [ngTemplateOutletContext]=\"{ $implicit: c }\"></ng-container>\r\n }\r\n </ul>\r\n\r\n @if (rootPage().hasMore) {\r\n <button type=\"button\" class=\"fly-comment-thread__load-more\" (click)=\"loadMoreRoot()\">\r\n {{ 'comment.load_more' | translate }}\r\n </button>\r\n }\r\n }\r\n</div>\r\n\r\n<!-- \u2500\u2500 Recursive comment node template \u2500\u2500 -->\r\n<ng-template #commentNode let-comment>\r\n <li class=\"fly-comment-thread__item\" [attr.data-depth]=\"comment.depth\">\r\n <div class=\"fly-comment-thread__row\">\r\n <div class=\"fly-comment-thread__avatar\" aria-hidden=\"true\">\r\n @if (comment.authorAvatarUrl) {\r\n <img [src]=\"comment.authorAvatarUrl\" alt=\"\" />\r\n } @else {\r\n <span class=\"fly-comment-thread__avatar-fallback\">{{ (comment.authorDisplayName || comment.authorUserId)[0] }}</span>\r\n }\r\n </div>\r\n\r\n <div class=\"fly-comment-thread__content\">\r\n <div class=\"fly-comment-thread__meta\">\r\n <span class=\"fly-comment-thread__author\">{{ comment.authorDisplayName || comment.authorUserId }}</span>\r\n <span class=\"fly-comment-thread__timestamp\">{{ formatDate(comment.createdAt) }}</span>\r\n @if (comment.isEdited && !comment.isDeleted) {\r\n <span class=\"fly-comment-thread__edited\">{{ 'comment.edited' | translate }}</span>\r\n }\r\n </div>\r\n\r\n @if (comment.isDeleted) {\r\n <p class=\"fly-comment-thread__tombstone\">{{ 'comment.deleted' | translate }}</p>\r\n } @else if (editingId() === comment.id) {\r\n <div class=\"fly-comment-thread__edit-box\">\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"3\"\r\n [ngModel]=\"comment.body\"\r\n (ngModelChange)=\"onEditDraftChange($event)\"></textarea>\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"cancelEdit()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-comment-thread__submit-btn\" (click)=\"saveEdit(comment)\">\r\n {{ 'comment.save' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n } @else {\r\n <p class=\"fly-comment-thread__text\">{{ comment.body }}</p>\r\n }\r\n\r\n @if (!isReadonly() && !comment.isDeleted && editingId() !== comment.id) {\r\n <div class=\"fly-comment-thread__actions\">\r\n @if (canReply()) {\r\n <button type=\"button\" class=\"fly-comment-thread__action-btn\" (click)=\"openReplyBox(comment.id)\">\r\n {{ 'comment.reply' | translate }}\r\n </button>\r\n }\r\n @if (canEditComment(comment)) {\r\n <button type=\"button\" class=\"fly-comment-thread__action-btn\" (click)=\"startEdit(comment)\">\r\n {{ 'comment.edit' | translate }}\r\n </button>\r\n }\r\n @if (canDeleteComment(comment)) {\r\n <button type=\"button\" class=\"fly-comment-thread__action-btn fly-comment-thread__action-btn--danger\" (click)=\"requestDelete(comment)\">\r\n {{ 'comment.delete' | translate }}\r\n </button>\r\n }\r\n @if (canReport(comment)) {\r\n <button type=\"button\" class=\"fly-comment-thread__action-btn\" (click)=\"openReport(comment)\">\r\n {{ 'comment.report' | translate }}\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n @if (confirmingDeleteId() === comment.id) {\r\n <div class=\"fly-comment-thread__confirm\">\r\n <span>{{ 'comment.delete_confirm' | translate }}</span>\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"cancelDelete()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-comment-thread__danger-btn\" (click)=\"confirmDelete(comment)\">\r\n {{ 'comment.delete' | translate }}\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (isReplyBoxOpen(comment.id)) {\r\n <div class=\"fly-comment-thread__composer fly-comment-thread__composer--reply\">\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"2\"\r\n [attr.aria-label]=\"'comment.reply_placeholder' | translate\"\r\n [placeholder]=\"'comment.reply_placeholder' | translate\"\r\n [ngModel]=\"replyDraft(comment.id)\"\r\n (ngModelChange)=\"onReplyDraftChange(comment.id, $event)\"></textarea>\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"closeReplyBox(comment.id)\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__submit-btn\"\r\n [disabled]=\"!replyDraft(comment.id).trim()\"\r\n (click)=\"submitReply(comment.id)\">\r\n {{ 'comment.post' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n\r\n @if (comment.replyCount > 0) {\r\n <button type=\"button\" class=\"fly-comment-thread__toggle-replies\" (click)=\"toggleReplies(comment)\">\r\n {{ (isExpanded(comment.id) ? 'comment.hide_replies' : 'comment.show_replies') | translate: { n: comment.replyCount } }}\r\n </button>\r\n }\r\n\r\n @if (isExpanded(comment.id)) {\r\n <ul class=\"fly-comment-thread__list fly-comment-thread__list--nested\" role=\"list\">\r\n @for (child of repliesFor(comment.id)?.items ?? []; track child.id) {\r\n <ng-container [ngTemplateOutlet]=\"commentNode\" [ngTemplateOutletContext]=\"{ $implicit: child }\"></ng-container>\r\n }\r\n </ul>\r\n @if (repliesFor(comment.id)?.hasMore) {\r\n <button type=\"button\" class=\"fly-comment-thread__load-more fly-comment-thread__load-more--nested\" (click)=\"loadMoreReplies(comment.id)\">\r\n {{ 'comment.load_more' | translate }}\r\n </button>\r\n }\r\n }\r\n </div>\r\n </div>\r\n </li>\r\n</ng-template>\r\n\r\n<!-- \u2500\u2500 Report dialog \u2500\u2500 -->\r\n@if (reportingId()) {\r\n <div class=\"fly-comment-thread__overlay\">\r\n <div class=\"fly-comment-thread__overlay-backdrop\" role=\"button\" tabindex=\"0\"\r\n [attr.aria-label]=\"'common.action.cancel' | translate\"\r\n (click)=\"closeReport()\"\r\n (keydown.enter)=\"closeReport()\"\r\n (keydown.space)=\"onOverlayBackdropSpace($event)\"></div>\r\n <div class=\"fly-comment-thread__dialog\" role=\"dialog\" [attr.aria-label]=\"'comment.report_title' | translate\">\r\n <h3 class=\"fly-comment-thread__dialog-title\">{{ 'comment.report_title' | translate }}</h3>\r\n\r\n <fieldset class=\"fly-comment-thread__reason-group\">\r\n <legend>{{ 'comment.report_reason_label' | translate }}</legend>\r\n @for (reason of reportReasons; track reason) {\r\n <label class=\"fly-comment-thread__reason-option\">\r\n <input\r\n type=\"radio\"\r\n name=\"fly-comment-report-reason\"\r\n [value]=\"reason\"\r\n [checked]=\"reportReason() === reason\"\r\n (change)=\"onReportReasonChange(reason)\" />\r\n <span>{{ 'comment.reason.' + reason | translate }}</span>\r\n </label>\r\n }\r\n </fieldset>\r\n\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"3\"\r\n [attr.aria-label]=\"'comment.report_detail_label' | translate\"\r\n [placeholder]=\"'comment.report_detail_label' | translate\"\r\n [ngModel]=\"reportDetail()\"\r\n (ngModelChange)=\"onReportDetailChange($event)\"></textarea>\r\n\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"closeReport()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-comment-thread__submit-btn\" [disabled]=\"!reportReason()\" (click)=\"submitReport()\">\r\n {{ 'comment.report_submit' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--surface-card, #fff);--_surface-hover: var(--surface-hover, #f1f5f9);--_border: var(--separator, var(--surface-border, #e2e8f0));--_text: var(--label-primary, var(--text-color, #0f172a));--_text-subtle: var(--label-secondary, var(--text-color-secondary, #64748b));--_text-tertiary: var(--label-tertiary, #94a3b8);--_fill: var(--fill-tertiary, #f3f4f6);--_accent: var(--accent);--_danger: var(--system-red, #ef4444);--_radius: 8px;display:block;inline-size:100%;color:var(--_text);font-size:13px}.fly-comment-thread__body{display:flex;flex-direction:column;gap:16px}.fly-comment-thread__header{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.fly-comment-thread__count{font-weight:700;font-size:14px}.fly-comment-thread__header-actions{display:flex;align-items:center;gap:10px}.fly-comment-thread__sort{display:inline-flex;border:1px solid var(--_border);border-radius:var(--_radius);overflow:hidden}.fly-comment-thread__sort-btn{padding:5px 12px;border:none;border-inline-end:1px solid var(--_border);background:transparent;color:var(--_text-subtle);font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__sort-btn:last-child{border-inline-end:none}.fly-comment-thread__sort-btn--active{background:var(--_accent);color:#fff}.fly-comment-thread__lock-btn{padding:5px 12px;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);color:var(--_text);font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__lock-btn:hover{background:var(--_surface-hover)}.fly-comment-thread__locked-notice{margin:0;padding:8px 12px;border-radius:var(--_radius);background:var(--_fill);color:var(--_text-subtle);font-size:12px}.fly-comment-thread__empty{margin:0;color:var(--_text-subtle);text-align:center;padding:16px 0}.fly-comment-thread__composer{display:flex;flex-direction:column;gap:8px}.fly-comment-thread__composer--reply{margin-block-start:8px}.fly-comment-thread__textarea{inline-size:100%;padding:8px 12px;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);color:var(--_text);font:inherit;line-height:1.5;resize:vertical}.fly-comment-thread__textarea:focus-visible{outline:2px solid var(--_accent);outline-offset:1px}.fly-comment-thread__composer-actions{display:flex;justify-content:flex-end;gap:8px}.fly-comment-thread__submit-btn{padding:6px 16px;border:none;border-radius:var(--_radius);background:var(--_accent);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__submit-btn:hover:not(:disabled){opacity:.9}.fly-comment-thread__submit-btn:disabled{opacity:.5;cursor:not-allowed}.fly-comment-thread__cancel-btn{padding:6px 14px;border:1px solid var(--_border);border-radius:var(--_radius);background:transparent;color:var(--_text-subtle);font:inherit;cursor:pointer}.fly-comment-thread__cancel-btn:hover{background:var(--_surface-hover)}.fly-comment-thread__danger-btn{padding:6px 14px;border:none;border-radius:var(--_radius);background:var(--_danger);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__danger-btn:hover{opacity:.9}.fly-comment-thread__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:14px}.fly-comment-thread__list--nested{margin-block-start:10px;margin-inline-start:20px;padding-inline-start:12px;border-inline-start:2px solid var(--_border);gap:12px}.fly-comment-thread__item{display:block}.fly-comment-thread__row{display:flex;gap:10px;align-items:flex-start}.fly-comment-thread__avatar{inline-size:32px;block-size:32px;border-radius:50%;overflow:hidden;flex:none;background:var(--_fill);display:flex;align-items:center;justify-content:center}.fly-comment-thread__avatar img{inline-size:100%;block-size:100%;object-fit:cover}.fly-comment-thread__avatar-fallback{font-weight:700;color:var(--_text-subtle);text-transform:uppercase}.fly-comment-thread__content{flex:1 1 auto;min-inline-size:0;display:flex;flex-direction:column;gap:4px}.fly-comment-thread__meta{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap}.fly-comment-thread__author{font-weight:700;color:var(--_text)}.fly-comment-thread__timestamp{font-size:11px;color:var(--_text-tertiary)}.fly-comment-thread__edited{font-size:11px;color:var(--_text-tertiary);font-style:italic}.fly-comment-thread__text{margin:0;color:var(--_text);line-height:1.5;white-space:pre-wrap;overflow-wrap:anywhere}.fly-comment-thread__tombstone{margin:0;color:var(--_text-tertiary);font-style:italic}.fly-comment-thread__edit-box{display:flex;flex-direction:column;gap:6px}.fly-comment-thread__actions{display:flex;gap:12px;margin-block-start:2px}.fly-comment-thread__action-btn{border:none;background:transparent;padding:0;color:var(--_text-subtle);font:inherit;font-size:12px;font-weight:600;cursor:pointer}.fly-comment-thread__action-btn:hover{color:var(--_accent);text-decoration:underline}.fly-comment-thread__action-btn--danger:hover{color:var(--_danger)}.fly-comment-thread__confirm{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:var(--_radius);background:var(--_fill);font-size:12px;color:var(--_text-subtle)}.fly-comment-thread__toggle-replies{align-self:flex-start;border:none;background:transparent;padding:0;margin-block-start:4px;color:var(--_accent);font:inherit;font-size:12px;font-weight:700;cursor:pointer}.fly-comment-thread__toggle-replies:hover{text-decoration:underline}.fly-comment-thread__load-more{align-self:flex-start;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);padding:6px 14px;color:var(--_text);font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__load-more:hover{background:var(--_surface-hover)}.fly-comment-thread__load-more--nested{margin-inline-start:20px}.fly-comment-thread__overlay{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;z-index:1000}.fly-comment-thread__overlay-backdrop{position:absolute;inset:0;background:#00000073;border:none;padding:0;margin:0;cursor:pointer}.fly-comment-thread__dialog{position:relative;z-index:1;inline-size:min(420px,90vw);max-block-size:85vh;overflow-y:auto;background:var(--_surface);border-radius:12px;padding:20px;display:flex;flex-direction:column;gap:14px;box-shadow:0 12px 32px #00000040}.fly-comment-thread__dialog-title{margin:0;font-size:16px;font-weight:700}.fly-comment-thread__reason-group{display:flex;flex-direction:column;gap:8px;margin:0;padding:0;border:none}.fly-comment-thread__reason-group legend{font-weight:600;padding:0;margin-block-end:4px}.fly-comment-thread__reason-option{display:flex;align-items:center;gap:8px;cursor:pointer}.fly-comment-thread__reason-option input{accent-color:var(--_accent);cursor:pointer}:host(.fly-comment-thread--readonly) .fly-comment-thread__composer,:host(.fly-comment-thread--readonly) .fly-comment-thread__actions{display:none}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
13615
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyCommentThreadComponent, isStandalone: true, selector: "fly-comment-thread", inputs: { rootPage: { classPropertyName: "rootPage", publicName: "rootPage", isSignal: true, isRequired: true, transformFunction: null }, repliesByParent: { classPropertyName: "repliesByParent", publicName: "repliesByParent", isSignal: true, isRequired: false, transformFunction: null }, sort: { classPropertyName: "sort", publicName: "sort", isSignal: true, isRequired: false, transformFunction: null }, isLocked: { classPropertyName: "isLocked", publicName: "isLocked", isSignal: true, isRequired: false, transformFunction: null }, currentUserId: { classPropertyName: "currentUserId", publicName: "currentUserId", isSignal: true, isRequired: false, transformFunction: null }, isReadonly: { classPropertyName: "isReadonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, canModerateThread: { classPropertyName: "canModerateThread", publicName: "canModerateThread", isSignal: true, isRequired: false, transformFunction: null }, resolveDisplayName: { classPropertyName: "resolveDisplayName", publicName: "resolveDisplayName", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { loadReplies: "loadReplies", loadMore: "loadMore", submit: "submit", edit: "edit", delete: "delete", report: "report", lockToggle: "lockToggle", sortChange: "sortChange" }, host: { properties: { "class.fly-comment-thread--locked": "isLocked()", "class.fly-comment-thread--readonly": "isReadonly()" }, classAttribute: "fly-comment-thread" }, ngImport: i0, template: "<div class=\"fly-comment-thread__body\">\r\n <!-- \u2500\u2500 Header: count + one combined sort/lock icon-button toolbar \u2500\u2500 -->\r\n <header class=\"fly-comment-thread__header\">\r\n <span class=\"fly-comment-thread__count\">\r\n {{ 'comment.count' | translate: { n: rootPage().total } }}\r\n </span>\r\n\r\n <div class=\"fly-comment-thread__toolbar\" role=\"group\" [attr.aria-label]=\"'comment.sort_label' | translate\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__icon-btn\"\r\n [class.fly-comment-thread__icon-btn--active]=\"sort() === 'newest'\"\r\n [attr.aria-pressed]=\"sort() === 'newest'\"\r\n [flyTooltip]=\"'comment.sort_newest' | translate\"\r\n [attr.aria-label]=\"'comment.sort_newest' | translate\"\r\n (click)=\"onSortPick('newest')\">\r\n <i class=\"pi pi-sort-amount-down\" aria-hidden=\"true\"></i>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__icon-btn\"\r\n [class.fly-comment-thread__icon-btn--active]=\"sort() === 'oldest'\"\r\n [attr.aria-pressed]=\"sort() === 'oldest'\"\r\n [flyTooltip]=\"'comment.sort_oldest' | translate\"\r\n [attr.aria-label]=\"'comment.sort_oldest' | translate\"\r\n (click)=\"onSortPick('oldest')\">\r\n <i class=\"pi pi-sort-amount-up\" aria-hidden=\"true\"></i>\r\n </button>\r\n\r\n @if (canModerateThread() && !isReadonly()) {\r\n <span class=\"fly-comment-thread__toolbar-sep\" aria-hidden=\"true\"></span>\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__icon-btn\"\r\n [class.fly-comment-thread__icon-btn--active]=\"isLocked()\"\r\n [attr.aria-pressed]=\"isLocked()\"\r\n [flyTooltip]=\"(isLocked() ? 'comment.unlock' : 'comment.lock') | translate\"\r\n [attr.aria-label]=\"(isLocked() ? 'comment.unlock' : 'comment.lock') | translate\"\r\n (click)=\"toggleLock()\">\r\n <i class=\"pi\" [class.pi-lock]=\"!isLocked()\" [class.pi-lock-open]=\"isLocked()\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n </div>\r\n </header>\r\n\r\n @if (isLocked()) {\r\n <p class=\"fly-comment-thread__locked-notice\">{{ 'comment.locked_notice' | translate }}</p>\r\n }\r\n\r\n <!-- \u2500\u2500 Root composer \u2500\u2500 -->\r\n @if (canReply()) {\r\n <div class=\"fly-comment-thread__composer\">\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"3\"\r\n [id]=\"fieldId('root-composer')\"\r\n [attr.aria-label]=\"'comment.composer_placeholder' | translate\"\r\n [placeholder]=\"'comment.composer_placeholder' | translate\"\r\n [ngModel]=\"rootDraft()\"\r\n (ngModelChange)=\"onRootDraftChange($event)\"></textarea>\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__submit-btn\"\r\n [disabled]=\"!rootDraft().trim()\"\r\n (click)=\"submitRoot()\">\r\n {{ 'comment.post' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- \u2500\u2500 Root comments \u2500\u2500 -->\r\n @if (rootPage().items.length === 0) {\r\n <p class=\"fly-comment-thread__empty\">{{ 'comment.empty' | translate }}</p>\r\n } @else {\r\n <ul class=\"fly-comment-thread__list\" role=\"list\">\r\n @for (c of rootPage().items; track c.id) {\r\n <ng-container [ngTemplateOutlet]=\"commentNode\" [ngTemplateOutletContext]=\"{ $implicit: c }\"></ng-container>\r\n }\r\n </ul>\r\n\r\n @if (rootPage().hasMore) {\r\n <button type=\"button\" class=\"fly-comment-thread__load-more\" (click)=\"loadMoreRoot()\">\r\n {{ 'comment.load_more' | translate }}\r\n </button>\r\n }\r\n }\r\n</div>\r\n\r\n<!-- \u2500\u2500 Recursive comment node template \u2500\u2500 -->\r\n<ng-template #commentNode let-comment>\r\n <li class=\"fly-comment-thread__item\" [attr.data-depth]=\"comment.depth\">\r\n <div class=\"fly-comment-thread__row\">\r\n <div class=\"fly-comment-thread__avatar\" aria-hidden=\"true\">\r\n @if (comment.authorAvatarUrl) {\r\n <img [src]=\"comment.authorAvatarUrl\" alt=\"\" />\r\n } @else {\r\n <span class=\"fly-comment-thread__avatar-fallback\">{{ authorInitial(comment) }}</span>\r\n }\r\n </div>\r\n\r\n <div class=\"fly-comment-thread__content\">\r\n <div class=\"fly-comment-thread__meta\">\r\n <span class=\"fly-comment-thread__author\">{{ authorLabel(comment) }}</span>\r\n <span class=\"fly-comment-thread__timestamp\">{{ formatDate(comment.createdAt) }}</span>\r\n @if (comment.isEdited && !comment.isDeleted) {\r\n <span class=\"fly-comment-thread__edited\">{{ 'comment.edited' | translate }}</span>\r\n }\r\n </div>\r\n\r\n @if (comment.isDeleted) {\r\n <p class=\"fly-comment-thread__tombstone\">{{ 'comment.deleted' | translate }}</p>\r\n } @else if (editingId() === comment.id) {\r\n <div class=\"fly-comment-thread__edit-box\">\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"3\"\r\n [ngModel]=\"comment.body\"\r\n (ngModelChange)=\"onEditDraftChange($event)\"></textarea>\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"cancelEdit()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-comment-thread__submit-btn\" (click)=\"saveEdit(comment)\">\r\n {{ 'comment.save' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"fly-comment-thread__text-row\">\r\n <p class=\"fly-comment-thread__text\">{{ comment.body }}</p>\r\n @if (!isReadonly()) {\r\n <div class=\"fly-comment-thread__actions\">\r\n @if (canReply()) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__icon-btn\"\r\n [flyTooltip]=\"'comment.reply' | translate\"\r\n [attr.aria-label]=\"'comment.reply' | translate\"\r\n (click)=\"openReplyBox(comment.id)\">\r\n <i class=\"pi pi-reply\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n @if (canEditComment(comment)) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__icon-btn\"\r\n [flyTooltip]=\"'comment.edit' | translate\"\r\n [attr.aria-label]=\"'comment.edit' | translate\"\r\n (click)=\"startEdit(comment)\">\r\n <i class=\"pi pi-pencil\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n @if (canDeleteComment(comment)) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__icon-btn fly-comment-thread__icon-btn--danger\"\r\n [flyTooltip]=\"'comment.delete' | translate\"\r\n [attr.aria-label]=\"'comment.delete' | translate\"\r\n (click)=\"requestDelete(comment)\">\r\n <i class=\"pi pi-trash\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n @if (canReport(comment)) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__icon-btn\"\r\n [flyTooltip]=\"'comment.report' | translate\"\r\n [attr.aria-label]=\"'comment.report' | translate\"\r\n (click)=\"openReport(comment)\">\r\n <i class=\"pi pi-flag\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n @if (confirmingDeleteId() === comment.id) {\r\n <div class=\"fly-comment-thread__confirm\">\r\n <span>{{ 'comment.delete_confirm' | translate }}</span>\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"cancelDelete()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-comment-thread__danger-btn\" (click)=\"confirmDelete(comment)\">\r\n {{ 'comment.delete' | translate }}\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (isReplyBoxOpen(comment.id)) {\r\n <div class=\"fly-comment-thread__composer fly-comment-thread__composer--reply\">\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"2\"\r\n [attr.aria-label]=\"'comment.reply_placeholder' | translate\"\r\n [placeholder]=\"'comment.reply_placeholder' | translate\"\r\n [ngModel]=\"replyDraft(comment.id)\"\r\n (ngModelChange)=\"onReplyDraftChange(comment.id, $event)\"></textarea>\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"closeReplyBox(comment.id)\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__submit-btn\"\r\n [disabled]=\"!replyDraft(comment.id).trim()\"\r\n (click)=\"submitReply(comment.id)\">\r\n {{ 'comment.post' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n\r\n @if (comment.replyCount > 0) {\r\n <button type=\"button\" class=\"fly-comment-thread__toggle-replies\" (click)=\"toggleReplies(comment)\">\r\n {{ (isExpanded(comment.id) ? 'comment.hide_replies' : 'comment.show_replies') | translate: { n: comment.replyCount } }}\r\n </button>\r\n }\r\n\r\n @if (isExpanded(comment.id)) {\r\n <ul class=\"fly-comment-thread__list fly-comment-thread__list--nested\" role=\"list\">\r\n @for (child of repliesFor(comment.id)?.items ?? []; track child.id) {\r\n <ng-container [ngTemplateOutlet]=\"commentNode\" [ngTemplateOutletContext]=\"{ $implicit: child }\"></ng-container>\r\n }\r\n </ul>\r\n @if (repliesFor(comment.id)?.hasMore) {\r\n <button type=\"button\" class=\"fly-comment-thread__load-more fly-comment-thread__load-more--nested\" (click)=\"loadMoreReplies(comment.id)\">\r\n {{ 'comment.load_more' | translate }}\r\n </button>\r\n }\r\n }\r\n </div>\r\n </div>\r\n </li>\r\n</ng-template>\r\n\r\n<!-- \u2500\u2500 Report dialog \u2500\u2500 -->\r\n@if (reportingId()) {\r\n <div class=\"fly-comment-thread__overlay\">\r\n <div class=\"fly-comment-thread__overlay-backdrop\" role=\"button\" tabindex=\"0\"\r\n [attr.aria-label]=\"'common.action.cancel' | translate\"\r\n (click)=\"closeReport()\"\r\n (keydown.enter)=\"closeReport()\"\r\n (keydown.space)=\"onOverlayBackdropSpace($event)\"></div>\r\n <div class=\"fly-comment-thread__dialog\" role=\"dialog\" [attr.aria-label]=\"'comment.report_title' | translate\">\r\n <h3 class=\"fly-comment-thread__dialog-title\">{{ 'comment.report_title' | translate }}</h3>\r\n\r\n <fieldset class=\"fly-comment-thread__reason-group\">\r\n <legend>{{ 'comment.report_reason_label' | translate }}</legend>\r\n @for (reason of reportReasons; track reason) {\r\n <label class=\"fly-comment-thread__reason-option\">\r\n <input\r\n type=\"radio\"\r\n name=\"fly-comment-report-reason\"\r\n [value]=\"reason\"\r\n [checked]=\"reportReason() === reason\"\r\n (change)=\"onReportReasonChange(reason)\" />\r\n <span>{{ 'comment.reason.' + reason | translate }}</span>\r\n </label>\r\n }\r\n </fieldset>\r\n\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"3\"\r\n [attr.aria-label]=\"'comment.report_detail_label' | translate\"\r\n [placeholder]=\"'comment.report_detail_label' | translate\"\r\n [ngModel]=\"reportDetail()\"\r\n (ngModelChange)=\"onReportDetailChange($event)\"></textarea>\r\n\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"closeReport()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-comment-thread__submit-btn\" [disabled]=\"!reportReason()\" (click)=\"submitReport()\">\r\n {{ 'comment.report_submit' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--surface-card, #fff);--_surface-hover: var(--surface-hover, #f1f5f9);--_border: var(--separator, var(--surface-border, #e2e8f0));--_text: var(--label-primary, var(--text-color, #0f172a));--_text-subtle: var(--label-secondary, var(--text-color-secondary, #64748b));--_text-tertiary: var(--label-tertiary, #94a3b8);--_fill: var(--fill-tertiary, #f3f4f6);--_accent: var(--accent);--_danger: var(--system-red, #ef4444);--_radius: 8px;display:block;inline-size:100%;color:var(--_text);font-size:13px}.fly-comment-thread__body{display:flex;flex-direction:column;gap:16px}.fly-comment-thread__header{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.fly-comment-thread__count{font-weight:700;font-size:14px}.fly-comment-thread__toolbar{display:inline-flex;align-items:center;gap:2px;padding:2px;border:1px solid var(--_border);border-radius:calc(var(--_radius) + 2px);background:var(--_surface)}.fly-comment-thread__toolbar-sep{inline-size:1px;block-size:18px;margin-inline:2px;background:var(--_border)}.fly-comment-thread__icon-btn{display:inline-flex;align-items:center;justify-content:center;inline-size:26px;block-size:26px;flex:none;padding:0;border:none;border-radius:50%;background:transparent;color:var(--_text-subtle);cursor:pointer;transition:background var(--t-state, .15s),color var(--t-state, .15s)}.fly-comment-thread__icon-btn .pi{font-size:13px}.fly-comment-thread__icon-btn:hover{background:var(--_surface-hover);color:var(--_text)}.fly-comment-thread__icon-btn:focus-visible{outline:2px solid var(--_accent);outline-offset:1px}.fly-comment-thread__icon-btn--active{background:var(--_accent);color:#fff}.fly-comment-thread__icon-btn--active:hover{background:var(--_accent);opacity:.9}.fly-comment-thread__icon-btn--danger:hover{background:var(--_danger);color:#fff}.fly-comment-thread__locked-notice{margin:0;padding:8px 12px;border-radius:var(--_radius);background:var(--_fill);color:var(--_text-subtle);font-size:12px}.fly-comment-thread__empty{margin:0;color:var(--_text-subtle);text-align:center;padding:16px 0}.fly-comment-thread__composer{display:flex;flex-direction:column;gap:8px}.fly-comment-thread__composer--reply{margin-block-start:8px}.fly-comment-thread__textarea{inline-size:100%;padding:8px 12px;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);color:var(--_text);font:inherit;line-height:1.5;resize:vertical}.fly-comment-thread__textarea:focus-visible{outline:2px solid var(--_accent);outline-offset:1px}.fly-comment-thread__composer-actions{display:flex;justify-content:flex-end;gap:8px}.fly-comment-thread__submit-btn{padding:6px 16px;border:none;border-radius:var(--_radius);background:var(--_accent);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__submit-btn:hover:not(:disabled){opacity:.9}.fly-comment-thread__submit-btn:disabled{opacity:.5;cursor:not-allowed}.fly-comment-thread__cancel-btn{padding:6px 14px;border:1px solid var(--_border);border-radius:var(--_radius);background:transparent;color:var(--_text-subtle);font:inherit;cursor:pointer}.fly-comment-thread__cancel-btn:hover{background:var(--_surface-hover)}.fly-comment-thread__danger-btn{padding:6px 14px;border:none;border-radius:var(--_radius);background:var(--_danger);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__danger-btn:hover{opacity:.9}.fly-comment-thread__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:14px}.fly-comment-thread__list--nested{margin-block-start:10px;margin-inline-start:20px;padding-inline-start:12px;border-inline-start:2px solid var(--_border);gap:12px}.fly-comment-thread__item{display:block}.fly-comment-thread__row{display:flex;gap:10px;align-items:flex-start}.fly-comment-thread__avatar{inline-size:32px;block-size:32px;border-radius:50%;overflow:hidden;flex:none;background:var(--_fill);display:flex;align-items:center;justify-content:center}.fly-comment-thread__avatar img{inline-size:100%;block-size:100%;object-fit:cover}.fly-comment-thread__avatar-fallback{font-weight:700;color:var(--_text-subtle);text-transform:uppercase}.fly-comment-thread__content{flex:1 1 auto;min-inline-size:0;display:flex;flex-direction:column;gap:4px}.fly-comment-thread__meta{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap}.fly-comment-thread__author{font-weight:700;color:var(--_text)}.fly-comment-thread__timestamp{font-size:11px;color:var(--_text-tertiary)}.fly-comment-thread__edited{font-size:11px;color:var(--_text-tertiary);font-style:italic}.fly-comment-thread__text-row{display:flex;align-items:flex-start;gap:4px}.fly-comment-thread__text{flex:1 1 auto;min-inline-size:0;margin:0;color:var(--_text);line-height:1.5;white-space:pre-wrap;overflow-wrap:anywhere}.fly-comment-thread__tombstone{margin:0;color:var(--_text-tertiary);font-style:italic}.fly-comment-thread__edit-box{display:flex;flex-direction:column;gap:6px}.fly-comment-thread__actions{display:flex;align-items:center;gap:2px;flex:none;margin-inline-start:auto}.fly-comment-thread__confirm{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:var(--_radius);background:var(--_fill);font-size:12px;color:var(--_text-subtle)}.fly-comment-thread__toggle-replies{align-self:flex-start;border:none;background:transparent;padding:0;margin-block-start:4px;color:var(--_accent);font:inherit;font-size:12px;font-weight:700;cursor:pointer}.fly-comment-thread__toggle-replies:hover{text-decoration:underline}.fly-comment-thread__load-more{align-self:flex-start;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);padding:6px 14px;color:var(--_text);font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__load-more:hover{background:var(--_surface-hover)}.fly-comment-thread__load-more--nested{margin-inline-start:20px}.fly-comment-thread__overlay{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;z-index:1000}.fly-comment-thread__overlay-backdrop{position:absolute;inset:0;background:#00000073;border:none;padding:0;margin:0;cursor:pointer}.fly-comment-thread__dialog{position:relative;z-index:1;inline-size:min(420px,90vw);max-block-size:85vh;overflow-y:auto;background:var(--_surface);border-radius:12px;padding:20px;display:flex;flex-direction:column;gap:14px;box-shadow:0 12px 32px #00000040}.fly-comment-thread__dialog-title{margin:0;font-size:16px;font-weight:700}.fly-comment-thread__reason-group{display:flex;flex-direction:column;gap:8px;margin:0;padding:0;border:none}.fly-comment-thread__reason-group legend{font-weight:600;padding:0;margin-block-end:4px}.fly-comment-thread__reason-option{display:flex;align-items:center;gap:8px;cursor:pointer}.fly-comment-thread__reason-option input{accent-color:var(--_accent);cursor:pointer}:host(.fly-comment-thread--readonly) .fly-comment-thread__composer,:host(.fly-comment-thread--readonly) .fly-comment-thread__actions{display:none}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: FlyTooltipDirective, selector: "[flyTooltip], [data-tooltip]", inputs: ["flyTooltip", "flyTooltipPlacement", "flyTooltipDisabled", "flyTooltipDelay"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
13176
13616
  }
13177
13617
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyCommentThreadComponent, decorators: [{
13178
13618
  type: Component,
13179
- args: [{ selector: 'fly-comment-thread', standalone: true, imports: [CommonModule, FormsModule, TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, host: {
13619
+ args: [{ selector: 'fly-comment-thread', standalone: true, imports: [CommonModule, FormsModule, TranslatePipe, FlyTooltipDirective], changeDetection: ChangeDetectionStrategy.OnPush, host: {
13180
13620
  class: 'fly-comment-thread',
13181
13621
  '[class.fly-comment-thread--locked]': 'isLocked()',
13182
13622
  '[class.fly-comment-thread--readonly]': 'isReadonly()',
13183
- }, template: "<div class=\"fly-comment-thread__body\">\r\n <!-- \u2500\u2500 Header: count + sort + lock toggle \u2500\u2500 -->\r\n <header class=\"fly-comment-thread__header\">\r\n <span class=\"fly-comment-thread__count\">\r\n {{ 'comment.count' | translate: { n: rootPage().total } }}\r\n </span>\r\n\r\n <div class=\"fly-comment-thread__header-actions\">\r\n <div class=\"fly-comment-thread__sort\" role=\"group\" [attr.aria-label]=\"'comment.sort_label' | translate\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__sort-btn\"\r\n [class.fly-comment-thread__sort-btn--active]=\"sort() === 'newest'\"\r\n (click)=\"onSortPick('newest')\">\r\n {{ 'comment.sort_newest' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__sort-btn\"\r\n [class.fly-comment-thread__sort-btn--active]=\"sort() === 'oldest'\"\r\n (click)=\"onSortPick('oldest')\">\r\n {{ 'comment.sort_oldest' | translate }}\r\n </button>\r\n </div>\r\n\r\n @if (canModerateThread() && !isReadonly()) {\r\n <button type=\"button\" class=\"fly-comment-thread__lock-btn\" (click)=\"toggleLock()\">\r\n {{ (isLocked() ? 'comment.unlock' : 'comment.lock') | translate }}\r\n </button>\r\n }\r\n </div>\r\n </header>\r\n\r\n @if (isLocked()) {\r\n <p class=\"fly-comment-thread__locked-notice\">{{ 'comment.locked_notice' | translate }}</p>\r\n }\r\n\r\n <!-- \u2500\u2500 Root composer \u2500\u2500 -->\r\n @if (canReply()) {\r\n <div class=\"fly-comment-thread__composer\">\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"3\"\r\n [id]=\"fieldId('root-composer')\"\r\n [attr.aria-label]=\"'comment.composer_placeholder' | translate\"\r\n [placeholder]=\"'comment.composer_placeholder' | translate\"\r\n [ngModel]=\"rootDraft()\"\r\n (ngModelChange)=\"onRootDraftChange($event)\"></textarea>\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__submit-btn\"\r\n [disabled]=\"!rootDraft().trim()\"\r\n (click)=\"submitRoot()\">\r\n {{ 'comment.post' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- \u2500\u2500 Root comments \u2500\u2500 -->\r\n @if (rootPage().items.length === 0) {\r\n <p class=\"fly-comment-thread__empty\">{{ 'comment.empty' | translate }}</p>\r\n } @else {\r\n <ul class=\"fly-comment-thread__list\" role=\"list\">\r\n @for (c of rootPage().items; track c.id) {\r\n <ng-container [ngTemplateOutlet]=\"commentNode\" [ngTemplateOutletContext]=\"{ $implicit: c }\"></ng-container>\r\n }\r\n </ul>\r\n\r\n @if (rootPage().hasMore) {\r\n <button type=\"button\" class=\"fly-comment-thread__load-more\" (click)=\"loadMoreRoot()\">\r\n {{ 'comment.load_more' | translate }}\r\n </button>\r\n }\r\n }\r\n</div>\r\n\r\n<!-- \u2500\u2500 Recursive comment node template \u2500\u2500 -->\r\n<ng-template #commentNode let-comment>\r\n <li class=\"fly-comment-thread__item\" [attr.data-depth]=\"comment.depth\">\r\n <div class=\"fly-comment-thread__row\">\r\n <div class=\"fly-comment-thread__avatar\" aria-hidden=\"true\">\r\n @if (comment.authorAvatarUrl) {\r\n <img [src]=\"comment.authorAvatarUrl\" alt=\"\" />\r\n } @else {\r\n <span class=\"fly-comment-thread__avatar-fallback\">{{ (comment.authorDisplayName || comment.authorUserId)[0] }}</span>\r\n }\r\n </div>\r\n\r\n <div class=\"fly-comment-thread__content\">\r\n <div class=\"fly-comment-thread__meta\">\r\n <span class=\"fly-comment-thread__author\">{{ comment.authorDisplayName || comment.authorUserId }}</span>\r\n <span class=\"fly-comment-thread__timestamp\">{{ formatDate(comment.createdAt) }}</span>\r\n @if (comment.isEdited && !comment.isDeleted) {\r\n <span class=\"fly-comment-thread__edited\">{{ 'comment.edited' | translate }}</span>\r\n }\r\n </div>\r\n\r\n @if (comment.isDeleted) {\r\n <p class=\"fly-comment-thread__tombstone\">{{ 'comment.deleted' | translate }}</p>\r\n } @else if (editingId() === comment.id) {\r\n <div class=\"fly-comment-thread__edit-box\">\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"3\"\r\n [ngModel]=\"comment.body\"\r\n (ngModelChange)=\"onEditDraftChange($event)\"></textarea>\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"cancelEdit()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-comment-thread__submit-btn\" (click)=\"saveEdit(comment)\">\r\n {{ 'comment.save' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n } @else {\r\n <p class=\"fly-comment-thread__text\">{{ comment.body }}</p>\r\n }\r\n\r\n @if (!isReadonly() && !comment.isDeleted && editingId() !== comment.id) {\r\n <div class=\"fly-comment-thread__actions\">\r\n @if (canReply()) {\r\n <button type=\"button\" class=\"fly-comment-thread__action-btn\" (click)=\"openReplyBox(comment.id)\">\r\n {{ 'comment.reply' | translate }}\r\n </button>\r\n }\r\n @if (canEditComment(comment)) {\r\n <button type=\"button\" class=\"fly-comment-thread__action-btn\" (click)=\"startEdit(comment)\">\r\n {{ 'comment.edit' | translate }}\r\n </button>\r\n }\r\n @if (canDeleteComment(comment)) {\r\n <button type=\"button\" class=\"fly-comment-thread__action-btn fly-comment-thread__action-btn--danger\" (click)=\"requestDelete(comment)\">\r\n {{ 'comment.delete' | translate }}\r\n </button>\r\n }\r\n @if (canReport(comment)) {\r\n <button type=\"button\" class=\"fly-comment-thread__action-btn\" (click)=\"openReport(comment)\">\r\n {{ 'comment.report' | translate }}\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n @if (confirmingDeleteId() === comment.id) {\r\n <div class=\"fly-comment-thread__confirm\">\r\n <span>{{ 'comment.delete_confirm' | translate }}</span>\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"cancelDelete()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-comment-thread__danger-btn\" (click)=\"confirmDelete(comment)\">\r\n {{ 'comment.delete' | translate }}\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (isReplyBoxOpen(comment.id)) {\r\n <div class=\"fly-comment-thread__composer fly-comment-thread__composer--reply\">\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"2\"\r\n [attr.aria-label]=\"'comment.reply_placeholder' | translate\"\r\n [placeholder]=\"'comment.reply_placeholder' | translate\"\r\n [ngModel]=\"replyDraft(comment.id)\"\r\n (ngModelChange)=\"onReplyDraftChange(comment.id, $event)\"></textarea>\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"closeReplyBox(comment.id)\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__submit-btn\"\r\n [disabled]=\"!replyDraft(comment.id).trim()\"\r\n (click)=\"submitReply(comment.id)\">\r\n {{ 'comment.post' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n\r\n @if (comment.replyCount > 0) {\r\n <button type=\"button\" class=\"fly-comment-thread__toggle-replies\" (click)=\"toggleReplies(comment)\">\r\n {{ (isExpanded(comment.id) ? 'comment.hide_replies' : 'comment.show_replies') | translate: { n: comment.replyCount } }}\r\n </button>\r\n }\r\n\r\n @if (isExpanded(comment.id)) {\r\n <ul class=\"fly-comment-thread__list fly-comment-thread__list--nested\" role=\"list\">\r\n @for (child of repliesFor(comment.id)?.items ?? []; track child.id) {\r\n <ng-container [ngTemplateOutlet]=\"commentNode\" [ngTemplateOutletContext]=\"{ $implicit: child }\"></ng-container>\r\n }\r\n </ul>\r\n @if (repliesFor(comment.id)?.hasMore) {\r\n <button type=\"button\" class=\"fly-comment-thread__load-more fly-comment-thread__load-more--nested\" (click)=\"loadMoreReplies(comment.id)\">\r\n {{ 'comment.load_more' | translate }}\r\n </button>\r\n }\r\n }\r\n </div>\r\n </div>\r\n </li>\r\n</ng-template>\r\n\r\n<!-- \u2500\u2500 Report dialog \u2500\u2500 -->\r\n@if (reportingId()) {\r\n <div class=\"fly-comment-thread__overlay\">\r\n <div class=\"fly-comment-thread__overlay-backdrop\" role=\"button\" tabindex=\"0\"\r\n [attr.aria-label]=\"'common.action.cancel' | translate\"\r\n (click)=\"closeReport()\"\r\n (keydown.enter)=\"closeReport()\"\r\n (keydown.space)=\"onOverlayBackdropSpace($event)\"></div>\r\n <div class=\"fly-comment-thread__dialog\" role=\"dialog\" [attr.aria-label]=\"'comment.report_title' | translate\">\r\n <h3 class=\"fly-comment-thread__dialog-title\">{{ 'comment.report_title' | translate }}</h3>\r\n\r\n <fieldset class=\"fly-comment-thread__reason-group\">\r\n <legend>{{ 'comment.report_reason_label' | translate }}</legend>\r\n @for (reason of reportReasons; track reason) {\r\n <label class=\"fly-comment-thread__reason-option\">\r\n <input\r\n type=\"radio\"\r\n name=\"fly-comment-report-reason\"\r\n [value]=\"reason\"\r\n [checked]=\"reportReason() === reason\"\r\n (change)=\"onReportReasonChange(reason)\" />\r\n <span>{{ 'comment.reason.' + reason | translate }}</span>\r\n </label>\r\n }\r\n </fieldset>\r\n\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"3\"\r\n [attr.aria-label]=\"'comment.report_detail_label' | translate\"\r\n [placeholder]=\"'comment.report_detail_label' | translate\"\r\n [ngModel]=\"reportDetail()\"\r\n (ngModelChange)=\"onReportDetailChange($event)\"></textarea>\r\n\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"closeReport()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-comment-thread__submit-btn\" [disabled]=\"!reportReason()\" (click)=\"submitReport()\">\r\n {{ 'comment.report_submit' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--surface-card, #fff);--_surface-hover: var(--surface-hover, #f1f5f9);--_border: var(--separator, var(--surface-border, #e2e8f0));--_text: var(--label-primary, var(--text-color, #0f172a));--_text-subtle: var(--label-secondary, var(--text-color-secondary, #64748b));--_text-tertiary: var(--label-tertiary, #94a3b8);--_fill: var(--fill-tertiary, #f3f4f6);--_accent: var(--accent);--_danger: var(--system-red, #ef4444);--_radius: 8px;display:block;inline-size:100%;color:var(--_text);font-size:13px}.fly-comment-thread__body{display:flex;flex-direction:column;gap:16px}.fly-comment-thread__header{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.fly-comment-thread__count{font-weight:700;font-size:14px}.fly-comment-thread__header-actions{display:flex;align-items:center;gap:10px}.fly-comment-thread__sort{display:inline-flex;border:1px solid var(--_border);border-radius:var(--_radius);overflow:hidden}.fly-comment-thread__sort-btn{padding:5px 12px;border:none;border-inline-end:1px solid var(--_border);background:transparent;color:var(--_text-subtle);font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__sort-btn:last-child{border-inline-end:none}.fly-comment-thread__sort-btn--active{background:var(--_accent);color:#fff}.fly-comment-thread__lock-btn{padding:5px 12px;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);color:var(--_text);font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__lock-btn:hover{background:var(--_surface-hover)}.fly-comment-thread__locked-notice{margin:0;padding:8px 12px;border-radius:var(--_radius);background:var(--_fill);color:var(--_text-subtle);font-size:12px}.fly-comment-thread__empty{margin:0;color:var(--_text-subtle);text-align:center;padding:16px 0}.fly-comment-thread__composer{display:flex;flex-direction:column;gap:8px}.fly-comment-thread__composer--reply{margin-block-start:8px}.fly-comment-thread__textarea{inline-size:100%;padding:8px 12px;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);color:var(--_text);font:inherit;line-height:1.5;resize:vertical}.fly-comment-thread__textarea:focus-visible{outline:2px solid var(--_accent);outline-offset:1px}.fly-comment-thread__composer-actions{display:flex;justify-content:flex-end;gap:8px}.fly-comment-thread__submit-btn{padding:6px 16px;border:none;border-radius:var(--_radius);background:var(--_accent);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__submit-btn:hover:not(:disabled){opacity:.9}.fly-comment-thread__submit-btn:disabled{opacity:.5;cursor:not-allowed}.fly-comment-thread__cancel-btn{padding:6px 14px;border:1px solid var(--_border);border-radius:var(--_radius);background:transparent;color:var(--_text-subtle);font:inherit;cursor:pointer}.fly-comment-thread__cancel-btn:hover{background:var(--_surface-hover)}.fly-comment-thread__danger-btn{padding:6px 14px;border:none;border-radius:var(--_radius);background:var(--_danger);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__danger-btn:hover{opacity:.9}.fly-comment-thread__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:14px}.fly-comment-thread__list--nested{margin-block-start:10px;margin-inline-start:20px;padding-inline-start:12px;border-inline-start:2px solid var(--_border);gap:12px}.fly-comment-thread__item{display:block}.fly-comment-thread__row{display:flex;gap:10px;align-items:flex-start}.fly-comment-thread__avatar{inline-size:32px;block-size:32px;border-radius:50%;overflow:hidden;flex:none;background:var(--_fill);display:flex;align-items:center;justify-content:center}.fly-comment-thread__avatar img{inline-size:100%;block-size:100%;object-fit:cover}.fly-comment-thread__avatar-fallback{font-weight:700;color:var(--_text-subtle);text-transform:uppercase}.fly-comment-thread__content{flex:1 1 auto;min-inline-size:0;display:flex;flex-direction:column;gap:4px}.fly-comment-thread__meta{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap}.fly-comment-thread__author{font-weight:700;color:var(--_text)}.fly-comment-thread__timestamp{font-size:11px;color:var(--_text-tertiary)}.fly-comment-thread__edited{font-size:11px;color:var(--_text-tertiary);font-style:italic}.fly-comment-thread__text{margin:0;color:var(--_text);line-height:1.5;white-space:pre-wrap;overflow-wrap:anywhere}.fly-comment-thread__tombstone{margin:0;color:var(--_text-tertiary);font-style:italic}.fly-comment-thread__edit-box{display:flex;flex-direction:column;gap:6px}.fly-comment-thread__actions{display:flex;gap:12px;margin-block-start:2px}.fly-comment-thread__action-btn{border:none;background:transparent;padding:0;color:var(--_text-subtle);font:inherit;font-size:12px;font-weight:600;cursor:pointer}.fly-comment-thread__action-btn:hover{color:var(--_accent);text-decoration:underline}.fly-comment-thread__action-btn--danger:hover{color:var(--_danger)}.fly-comment-thread__confirm{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:var(--_radius);background:var(--_fill);font-size:12px;color:var(--_text-subtle)}.fly-comment-thread__toggle-replies{align-self:flex-start;border:none;background:transparent;padding:0;margin-block-start:4px;color:var(--_accent);font:inherit;font-size:12px;font-weight:700;cursor:pointer}.fly-comment-thread__toggle-replies:hover{text-decoration:underline}.fly-comment-thread__load-more{align-self:flex-start;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);padding:6px 14px;color:var(--_text);font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__load-more:hover{background:var(--_surface-hover)}.fly-comment-thread__load-more--nested{margin-inline-start:20px}.fly-comment-thread__overlay{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;z-index:1000}.fly-comment-thread__overlay-backdrop{position:absolute;inset:0;background:#00000073;border:none;padding:0;margin:0;cursor:pointer}.fly-comment-thread__dialog{position:relative;z-index:1;inline-size:min(420px,90vw);max-block-size:85vh;overflow-y:auto;background:var(--_surface);border-radius:12px;padding:20px;display:flex;flex-direction:column;gap:14px;box-shadow:0 12px 32px #00000040}.fly-comment-thread__dialog-title{margin:0;font-size:16px;font-weight:700}.fly-comment-thread__reason-group{display:flex;flex-direction:column;gap:8px;margin:0;padding:0;border:none}.fly-comment-thread__reason-group legend{font-weight:600;padding:0;margin-block-end:4px}.fly-comment-thread__reason-option{display:flex;align-items:center;gap:8px;cursor:pointer}.fly-comment-thread__reason-option input{accent-color:var(--_accent);cursor:pointer}:host(.fly-comment-thread--readonly) .fly-comment-thread__composer,:host(.fly-comment-thread--readonly) .fly-comment-thread__actions{display:none}\n"] }]
13184
- }], propDecorators: { rootPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootPage", required: true }] }], repliesByParent: [{ type: i0.Input, args: [{ isSignal: true, alias: "repliesByParent", required: false }] }], sort: [{ type: i0.Input, args: [{ isSignal: true, alias: "sort", required: false }] }], isLocked: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLocked", required: false }] }], currentUserId: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentUserId", required: false }] }], isReadonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], canModerateThread: [{ type: i0.Input, args: [{ isSignal: true, alias: "canModerateThread", required: false }] }], loadReplies: [{ type: i0.Output, args: ["loadReplies"] }], loadMore: [{ type: i0.Output, args: ["loadMore"] }], submit: [{ type: i0.Output, args: ["submit"] }], edit: [{ type: i0.Output, args: ["edit"] }], delete: [{ type: i0.Output, args: ["delete"] }], report: [{ type: i0.Output, args: ["report"] }], lockToggle: [{ type: i0.Output, args: ["lockToggle"] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }] } });
13623
+ }, template: "<div class=\"fly-comment-thread__body\">\r\n <!-- \u2500\u2500 Header: count + one combined sort/lock icon-button toolbar \u2500\u2500 -->\r\n <header class=\"fly-comment-thread__header\">\r\n <span class=\"fly-comment-thread__count\">\r\n {{ 'comment.count' | translate: { n: rootPage().total } }}\r\n </span>\r\n\r\n <div class=\"fly-comment-thread__toolbar\" role=\"group\" [attr.aria-label]=\"'comment.sort_label' | translate\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__icon-btn\"\r\n [class.fly-comment-thread__icon-btn--active]=\"sort() === 'newest'\"\r\n [attr.aria-pressed]=\"sort() === 'newest'\"\r\n [flyTooltip]=\"'comment.sort_newest' | translate\"\r\n [attr.aria-label]=\"'comment.sort_newest' | translate\"\r\n (click)=\"onSortPick('newest')\">\r\n <i class=\"pi pi-sort-amount-down\" aria-hidden=\"true\"></i>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__icon-btn\"\r\n [class.fly-comment-thread__icon-btn--active]=\"sort() === 'oldest'\"\r\n [attr.aria-pressed]=\"sort() === 'oldest'\"\r\n [flyTooltip]=\"'comment.sort_oldest' | translate\"\r\n [attr.aria-label]=\"'comment.sort_oldest' | translate\"\r\n (click)=\"onSortPick('oldest')\">\r\n <i class=\"pi pi-sort-amount-up\" aria-hidden=\"true\"></i>\r\n </button>\r\n\r\n @if (canModerateThread() && !isReadonly()) {\r\n <span class=\"fly-comment-thread__toolbar-sep\" aria-hidden=\"true\"></span>\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__icon-btn\"\r\n [class.fly-comment-thread__icon-btn--active]=\"isLocked()\"\r\n [attr.aria-pressed]=\"isLocked()\"\r\n [flyTooltip]=\"(isLocked() ? 'comment.unlock' : 'comment.lock') | translate\"\r\n [attr.aria-label]=\"(isLocked() ? 'comment.unlock' : 'comment.lock') | translate\"\r\n (click)=\"toggleLock()\">\r\n <i class=\"pi\" [class.pi-lock]=\"!isLocked()\" [class.pi-lock-open]=\"isLocked()\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n </div>\r\n </header>\r\n\r\n @if (isLocked()) {\r\n <p class=\"fly-comment-thread__locked-notice\">{{ 'comment.locked_notice' | translate }}</p>\r\n }\r\n\r\n <!-- \u2500\u2500 Root composer \u2500\u2500 -->\r\n @if (canReply()) {\r\n <div class=\"fly-comment-thread__composer\">\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"3\"\r\n [id]=\"fieldId('root-composer')\"\r\n [attr.aria-label]=\"'comment.composer_placeholder' | translate\"\r\n [placeholder]=\"'comment.composer_placeholder' | translate\"\r\n [ngModel]=\"rootDraft()\"\r\n (ngModelChange)=\"onRootDraftChange($event)\"></textarea>\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__submit-btn\"\r\n [disabled]=\"!rootDraft().trim()\"\r\n (click)=\"submitRoot()\">\r\n {{ 'comment.post' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- \u2500\u2500 Root comments \u2500\u2500 -->\r\n @if (rootPage().items.length === 0) {\r\n <p class=\"fly-comment-thread__empty\">{{ 'comment.empty' | translate }}</p>\r\n } @else {\r\n <ul class=\"fly-comment-thread__list\" role=\"list\">\r\n @for (c of rootPage().items; track c.id) {\r\n <ng-container [ngTemplateOutlet]=\"commentNode\" [ngTemplateOutletContext]=\"{ $implicit: c }\"></ng-container>\r\n }\r\n </ul>\r\n\r\n @if (rootPage().hasMore) {\r\n <button type=\"button\" class=\"fly-comment-thread__load-more\" (click)=\"loadMoreRoot()\">\r\n {{ 'comment.load_more' | translate }}\r\n </button>\r\n }\r\n }\r\n</div>\r\n\r\n<!-- \u2500\u2500 Recursive comment node template \u2500\u2500 -->\r\n<ng-template #commentNode let-comment>\r\n <li class=\"fly-comment-thread__item\" [attr.data-depth]=\"comment.depth\">\r\n <div class=\"fly-comment-thread__row\">\r\n <div class=\"fly-comment-thread__avatar\" aria-hidden=\"true\">\r\n @if (comment.authorAvatarUrl) {\r\n <img [src]=\"comment.authorAvatarUrl\" alt=\"\" />\r\n } @else {\r\n <span class=\"fly-comment-thread__avatar-fallback\">{{ authorInitial(comment) }}</span>\r\n }\r\n </div>\r\n\r\n <div class=\"fly-comment-thread__content\">\r\n <div class=\"fly-comment-thread__meta\">\r\n <span class=\"fly-comment-thread__author\">{{ authorLabel(comment) }}</span>\r\n <span class=\"fly-comment-thread__timestamp\">{{ formatDate(comment.createdAt) }}</span>\r\n @if (comment.isEdited && !comment.isDeleted) {\r\n <span class=\"fly-comment-thread__edited\">{{ 'comment.edited' | translate }}</span>\r\n }\r\n </div>\r\n\r\n @if (comment.isDeleted) {\r\n <p class=\"fly-comment-thread__tombstone\">{{ 'comment.deleted' | translate }}</p>\r\n } @else if (editingId() === comment.id) {\r\n <div class=\"fly-comment-thread__edit-box\">\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"3\"\r\n [ngModel]=\"comment.body\"\r\n (ngModelChange)=\"onEditDraftChange($event)\"></textarea>\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"cancelEdit()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-comment-thread__submit-btn\" (click)=\"saveEdit(comment)\">\r\n {{ 'comment.save' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"fly-comment-thread__text-row\">\r\n <p class=\"fly-comment-thread__text\">{{ comment.body }}</p>\r\n @if (!isReadonly()) {\r\n <div class=\"fly-comment-thread__actions\">\r\n @if (canReply()) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__icon-btn\"\r\n [flyTooltip]=\"'comment.reply' | translate\"\r\n [attr.aria-label]=\"'comment.reply' | translate\"\r\n (click)=\"openReplyBox(comment.id)\">\r\n <i class=\"pi pi-reply\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n @if (canEditComment(comment)) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__icon-btn\"\r\n [flyTooltip]=\"'comment.edit' | translate\"\r\n [attr.aria-label]=\"'comment.edit' | translate\"\r\n (click)=\"startEdit(comment)\">\r\n <i class=\"pi pi-pencil\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n @if (canDeleteComment(comment)) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__icon-btn fly-comment-thread__icon-btn--danger\"\r\n [flyTooltip]=\"'comment.delete' | translate\"\r\n [attr.aria-label]=\"'comment.delete' | translate\"\r\n (click)=\"requestDelete(comment)\">\r\n <i class=\"pi pi-trash\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n @if (canReport(comment)) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__icon-btn\"\r\n [flyTooltip]=\"'comment.report' | translate\"\r\n [attr.aria-label]=\"'comment.report' | translate\"\r\n (click)=\"openReport(comment)\">\r\n <i class=\"pi pi-flag\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n @if (confirmingDeleteId() === comment.id) {\r\n <div class=\"fly-comment-thread__confirm\">\r\n <span>{{ 'comment.delete_confirm' | translate }}</span>\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"cancelDelete()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-comment-thread__danger-btn\" (click)=\"confirmDelete(comment)\">\r\n {{ 'comment.delete' | translate }}\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (isReplyBoxOpen(comment.id)) {\r\n <div class=\"fly-comment-thread__composer fly-comment-thread__composer--reply\">\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"2\"\r\n [attr.aria-label]=\"'comment.reply_placeholder' | translate\"\r\n [placeholder]=\"'comment.reply_placeholder' | translate\"\r\n [ngModel]=\"replyDraft(comment.id)\"\r\n (ngModelChange)=\"onReplyDraftChange(comment.id, $event)\"></textarea>\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"closeReplyBox(comment.id)\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fly-comment-thread__submit-btn\"\r\n [disabled]=\"!replyDraft(comment.id).trim()\"\r\n (click)=\"submitReply(comment.id)\">\r\n {{ 'comment.post' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n\r\n @if (comment.replyCount > 0) {\r\n <button type=\"button\" class=\"fly-comment-thread__toggle-replies\" (click)=\"toggleReplies(comment)\">\r\n {{ (isExpanded(comment.id) ? 'comment.hide_replies' : 'comment.show_replies') | translate: { n: comment.replyCount } }}\r\n </button>\r\n }\r\n\r\n @if (isExpanded(comment.id)) {\r\n <ul class=\"fly-comment-thread__list fly-comment-thread__list--nested\" role=\"list\">\r\n @for (child of repliesFor(comment.id)?.items ?? []; track child.id) {\r\n <ng-container [ngTemplateOutlet]=\"commentNode\" [ngTemplateOutletContext]=\"{ $implicit: child }\"></ng-container>\r\n }\r\n </ul>\r\n @if (repliesFor(comment.id)?.hasMore) {\r\n <button type=\"button\" class=\"fly-comment-thread__load-more fly-comment-thread__load-more--nested\" (click)=\"loadMoreReplies(comment.id)\">\r\n {{ 'comment.load_more' | translate }}\r\n </button>\r\n }\r\n }\r\n </div>\r\n </div>\r\n </li>\r\n</ng-template>\r\n\r\n<!-- \u2500\u2500 Report dialog \u2500\u2500 -->\r\n@if (reportingId()) {\r\n <div class=\"fly-comment-thread__overlay\">\r\n <div class=\"fly-comment-thread__overlay-backdrop\" role=\"button\" tabindex=\"0\"\r\n [attr.aria-label]=\"'common.action.cancel' | translate\"\r\n (click)=\"closeReport()\"\r\n (keydown.enter)=\"closeReport()\"\r\n (keydown.space)=\"onOverlayBackdropSpace($event)\"></div>\r\n <div class=\"fly-comment-thread__dialog\" role=\"dialog\" [attr.aria-label]=\"'comment.report_title' | translate\">\r\n <h3 class=\"fly-comment-thread__dialog-title\">{{ 'comment.report_title' | translate }}</h3>\r\n\r\n <fieldset class=\"fly-comment-thread__reason-group\">\r\n <legend>{{ 'comment.report_reason_label' | translate }}</legend>\r\n @for (reason of reportReasons; track reason) {\r\n <label class=\"fly-comment-thread__reason-option\">\r\n <input\r\n type=\"radio\"\r\n name=\"fly-comment-report-reason\"\r\n [value]=\"reason\"\r\n [checked]=\"reportReason() === reason\"\r\n (change)=\"onReportReasonChange(reason)\" />\r\n <span>{{ 'comment.reason.' + reason | translate }}</span>\r\n </label>\r\n }\r\n </fieldset>\r\n\r\n <textarea\r\n class=\"fly-comment-thread__textarea\"\r\n rows=\"3\"\r\n [attr.aria-label]=\"'comment.report_detail_label' | translate\"\r\n [placeholder]=\"'comment.report_detail_label' | translate\"\r\n [ngModel]=\"reportDetail()\"\r\n (ngModelChange)=\"onReportDetailChange($event)\"></textarea>\r\n\r\n <div class=\"fly-comment-thread__composer-actions\">\r\n <button type=\"button\" class=\"fly-comment-thread__cancel-btn\" (click)=\"closeReport()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-comment-thread__submit-btn\" [disabled]=\"!reportReason()\" (click)=\"submitReport()\">\r\n {{ 'comment.report_submit' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--surface-card, #fff);--_surface-hover: var(--surface-hover, #f1f5f9);--_border: var(--separator, var(--surface-border, #e2e8f0));--_text: var(--label-primary, var(--text-color, #0f172a));--_text-subtle: var(--label-secondary, var(--text-color-secondary, #64748b));--_text-tertiary: var(--label-tertiary, #94a3b8);--_fill: var(--fill-tertiary, #f3f4f6);--_accent: var(--accent);--_danger: var(--system-red, #ef4444);--_radius: 8px;display:block;inline-size:100%;color:var(--_text);font-size:13px}.fly-comment-thread__body{display:flex;flex-direction:column;gap:16px}.fly-comment-thread__header{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.fly-comment-thread__count{font-weight:700;font-size:14px}.fly-comment-thread__toolbar{display:inline-flex;align-items:center;gap:2px;padding:2px;border:1px solid var(--_border);border-radius:calc(var(--_radius) + 2px);background:var(--_surface)}.fly-comment-thread__toolbar-sep{inline-size:1px;block-size:18px;margin-inline:2px;background:var(--_border)}.fly-comment-thread__icon-btn{display:inline-flex;align-items:center;justify-content:center;inline-size:26px;block-size:26px;flex:none;padding:0;border:none;border-radius:50%;background:transparent;color:var(--_text-subtle);cursor:pointer;transition:background var(--t-state, .15s),color var(--t-state, .15s)}.fly-comment-thread__icon-btn .pi{font-size:13px}.fly-comment-thread__icon-btn:hover{background:var(--_surface-hover);color:var(--_text)}.fly-comment-thread__icon-btn:focus-visible{outline:2px solid var(--_accent);outline-offset:1px}.fly-comment-thread__icon-btn--active{background:var(--_accent);color:#fff}.fly-comment-thread__icon-btn--active:hover{background:var(--_accent);opacity:.9}.fly-comment-thread__icon-btn--danger:hover{background:var(--_danger);color:#fff}.fly-comment-thread__locked-notice{margin:0;padding:8px 12px;border-radius:var(--_radius);background:var(--_fill);color:var(--_text-subtle);font-size:12px}.fly-comment-thread__empty{margin:0;color:var(--_text-subtle);text-align:center;padding:16px 0}.fly-comment-thread__composer{display:flex;flex-direction:column;gap:8px}.fly-comment-thread__composer--reply{margin-block-start:8px}.fly-comment-thread__textarea{inline-size:100%;padding:8px 12px;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);color:var(--_text);font:inherit;line-height:1.5;resize:vertical}.fly-comment-thread__textarea:focus-visible{outline:2px solid var(--_accent);outline-offset:1px}.fly-comment-thread__composer-actions{display:flex;justify-content:flex-end;gap:8px}.fly-comment-thread__submit-btn{padding:6px 16px;border:none;border-radius:var(--_radius);background:var(--_accent);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__submit-btn:hover:not(:disabled){opacity:.9}.fly-comment-thread__submit-btn:disabled{opacity:.5;cursor:not-allowed}.fly-comment-thread__cancel-btn{padding:6px 14px;border:1px solid var(--_border);border-radius:var(--_radius);background:transparent;color:var(--_text-subtle);font:inherit;cursor:pointer}.fly-comment-thread__cancel-btn:hover{background:var(--_surface-hover)}.fly-comment-thread__danger-btn{padding:6px 14px;border:none;border-radius:var(--_radius);background:var(--_danger);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__danger-btn:hover{opacity:.9}.fly-comment-thread__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:14px}.fly-comment-thread__list--nested{margin-block-start:10px;margin-inline-start:20px;padding-inline-start:12px;border-inline-start:2px solid var(--_border);gap:12px}.fly-comment-thread__item{display:block}.fly-comment-thread__row{display:flex;gap:10px;align-items:flex-start}.fly-comment-thread__avatar{inline-size:32px;block-size:32px;border-radius:50%;overflow:hidden;flex:none;background:var(--_fill);display:flex;align-items:center;justify-content:center}.fly-comment-thread__avatar img{inline-size:100%;block-size:100%;object-fit:cover}.fly-comment-thread__avatar-fallback{font-weight:700;color:var(--_text-subtle);text-transform:uppercase}.fly-comment-thread__content{flex:1 1 auto;min-inline-size:0;display:flex;flex-direction:column;gap:4px}.fly-comment-thread__meta{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap}.fly-comment-thread__author{font-weight:700;color:var(--_text)}.fly-comment-thread__timestamp{font-size:11px;color:var(--_text-tertiary)}.fly-comment-thread__edited{font-size:11px;color:var(--_text-tertiary);font-style:italic}.fly-comment-thread__text-row{display:flex;align-items:flex-start;gap:4px}.fly-comment-thread__text{flex:1 1 auto;min-inline-size:0;margin:0;color:var(--_text);line-height:1.5;white-space:pre-wrap;overflow-wrap:anywhere}.fly-comment-thread__tombstone{margin:0;color:var(--_text-tertiary);font-style:italic}.fly-comment-thread__edit-box{display:flex;flex-direction:column;gap:6px}.fly-comment-thread__actions{display:flex;align-items:center;gap:2px;flex:none;margin-inline-start:auto}.fly-comment-thread__confirm{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:var(--_radius);background:var(--_fill);font-size:12px;color:var(--_text-subtle)}.fly-comment-thread__toggle-replies{align-self:flex-start;border:none;background:transparent;padding:0;margin-block-start:4px;color:var(--_accent);font:inherit;font-size:12px;font-weight:700;cursor:pointer}.fly-comment-thread__toggle-replies:hover{text-decoration:underline}.fly-comment-thread__load-more{align-self:flex-start;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);padding:6px 14px;color:var(--_text);font:inherit;font-weight:600;cursor:pointer}.fly-comment-thread__load-more:hover{background:var(--_surface-hover)}.fly-comment-thread__load-more--nested{margin-inline-start:20px}.fly-comment-thread__overlay{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;z-index:1000}.fly-comment-thread__overlay-backdrop{position:absolute;inset:0;background:#00000073;border:none;padding:0;margin:0;cursor:pointer}.fly-comment-thread__dialog{position:relative;z-index:1;inline-size:min(420px,90vw);max-block-size:85vh;overflow-y:auto;background:var(--_surface);border-radius:12px;padding:20px;display:flex;flex-direction:column;gap:14px;box-shadow:0 12px 32px #00000040}.fly-comment-thread__dialog-title{margin:0;font-size:16px;font-weight:700}.fly-comment-thread__reason-group{display:flex;flex-direction:column;gap:8px;margin:0;padding:0;border:none}.fly-comment-thread__reason-group legend{font-weight:600;padding:0;margin-block-end:4px}.fly-comment-thread__reason-option{display:flex;align-items:center;gap:8px;cursor:pointer}.fly-comment-thread__reason-option input{accent-color:var(--_accent);cursor:pointer}:host(.fly-comment-thread--readonly) .fly-comment-thread__composer,:host(.fly-comment-thread--readonly) .fly-comment-thread__actions{display:none}\n"] }]
13624
+ }], propDecorators: { rootPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootPage", required: true }] }], repliesByParent: [{ type: i0.Input, args: [{ isSignal: true, alias: "repliesByParent", required: false }] }], sort: [{ type: i0.Input, args: [{ isSignal: true, alias: "sort", required: false }] }], isLocked: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLocked", required: false }] }], currentUserId: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentUserId", required: false }] }], isReadonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], canModerateThread: [{ type: i0.Input, args: [{ isSignal: true, alias: "canModerateThread", required: false }] }], resolveDisplayName: [{ type: i0.Input, args: [{ isSignal: true, alias: "resolveDisplayName", required: false }] }], loadReplies: [{ type: i0.Output, args: ["loadReplies"] }], loadMore: [{ type: i0.Output, args: ["loadMore"] }], submit: [{ type: i0.Output, args: ["submit"] }], edit: [{ type: i0.Output, args: ["edit"] }], delete: [{ type: i0.Output, args: ["delete"] }], report: [{ type: i0.Output, args: ["report"] }], lockToggle: [{ type: i0.Output, args: ["lockToggle"] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }] } });
13185
13625
 
13186
13626
  // ── Vendored synchronous SHA-256 (FIPS 180-4) ────────────────────────────────
13187
13627
  // Why this exists: `fly-captcha`'s PoW solver must brute-force up to
@@ -15422,32 +15862,56 @@ function isChunkLoadError(err) {
15422
15862
  return looksLikeModuleUrl && looksLikeFetchStatus;
15423
15863
  }
15424
15864
  /**
15425
- * sessionStorage key recording that this tab session's single chunk-error
15426
- * reload was already spent. It persists across the reload it triggers (an
15427
- * in-memory flag resets on reload and cannot guard a loop).
15865
+ * sessionStorage key recording WHEN this tab session last spent its chunk-error
15866
+ * reload (epoch ms). It persists across the reload it triggers (an in-memory
15867
+ * flag resets on reload and cannot guard a loop).
15428
15868
  */
15429
15869
  const FLY_CHUNK_RELOAD_FLAG = 'fly:chunk-reload-attempted';
15430
15870
  /**
15431
- * One-shot self-heal for a stale client after a redeploy. Permits exactly ONE
15432
- * reload per tab session, so a reload that keeps failing (a chunk truly missing
15433
- * from the current build, or a genuinely-down host) does NOT loop.
15871
+ * How long a spent self-heal attempt stays spent. Within the window a repeat
15872
+ * failure falls through to the caller's error UI (loop guard); after it the
15873
+ * heal re-arms. A permanent one-shot stranded long-lived tabs on environments
15874
+ * that redeploy repeatedly (e.g. a Helm upgrade→rollback→retry cycle flips the
15875
+ * served build several times in an afternoon — each flip needs its own reload,
15876
+ * but only the first ever got one). 10 min is far above any error→reload→error
15877
+ * loop period (seconds) while well below the gap between distinct redeploys.
15878
+ */
15879
+ const FLY_RELOAD_REARM_MS = 10 * 60 * 1000;
15880
+ /**
15881
+ * Self-heal for a stale client after a redeploy. Permits ONE reload per
15882
+ * {@link FLY_RELOAD_REARM_MS} window per tab session, so a reload that keeps
15883
+ * failing (a chunk truly missing from the current build, or a genuinely-down
15884
+ * host) does NOT loop — but a LATER redeploy re-arms it.
15434
15885
  *
15435
15886
  * @returns `true` when a reload was initiated (the page will navigate away);
15436
- * `false` when the session's reload was already spent or sessionStorage is
15887
+ * `false` when the window's reload was already spent or sessionStorage is
15437
15888
  * unavailable — callers should surface a fallback / stop rather than spin.
15438
15889
  */
15439
15890
  function reloadOnceForChunkError() {
15891
+ if (!trySpendHealAttempt(FLY_CHUNK_RELOAD_FLAG))
15892
+ return false;
15893
+ window.location.reload();
15894
+ return true;
15895
+ }
15896
+ /**
15897
+ * Records a heal attempt under `key` unless one was already recorded within the
15898
+ * last {@link FLY_RELOAD_REARM_MS}. A legacy `'1'` value (pre-timestamp builds)
15899
+ * parses to epoch 1 and therefore counts as expired — one extra reload on
15900
+ * upgrade, never a loop. Returns `false` on sessionStorage failure (private
15901
+ * mode / quota / non-browser): cannot record the attempt, so cannot prove a
15902
+ * reload wouldn't loop — prefer the caller's fallback over a spin.
15903
+ */
15904
+ function trySpendHealAttempt(key) {
15440
15905
  try {
15441
- if (sessionStorage.getItem(FLY_CHUNK_RELOAD_FLAG))
15906
+ const last = Number(sessionStorage.getItem(key));
15907
+ if (Number.isFinite(last) && last > 0 && Date.now() - last < FLY_RELOAD_REARM_MS) {
15442
15908
  return false;
15443
- sessionStorage.setItem(FLY_CHUNK_RELOAD_FLAG, '1');
15909
+ }
15910
+ sessionStorage.setItem(key, String(Date.now()));
15444
15911
  }
15445
15912
  catch {
15446
- // Private mode / quota / non-browser: cannot record the attempt, so cannot
15447
- // prove a reload wouldn't loop. Prefer the fallback over a spin.
15448
15913
  return false;
15449
15914
  }
15450
- window.location.reload();
15451
15915
  return true;
15452
15916
  }
15453
15917
  /**
@@ -15474,10 +15938,11 @@ function isNativeFederationCacheError(err) {
15474
15938
  return /Unable to resolve specifier ['"]?@nf-internal\/chunk-/i.test(msg);
15475
15939
  }
15476
15940
  /**
15477
- * sessionStorage key recording that this tab session's single Native-Federation
15478
- * cache-heal (revalidation sweep + reload) was already spent separate from
15479
- * {@link FLY_CHUNK_RELOAD_FLAG} so a plain chunk-404 reload and a shared-bundle
15480
- * revalidation sweep each get one independent attempt.
15941
+ * sessionStorage key recording WHEN this tab session last spent its
15942
+ * Native-Federation cache-heal (revalidation sweep + reload; epoch ms)
15943
+ * separate from {@link FLY_CHUNK_RELOAD_FLAG} so a plain chunk-404 reload and a
15944
+ * shared-bundle revalidation sweep each get one independent attempt per
15945
+ * {@link FLY_RELOAD_REARM_MS} window.
15481
15946
  */
15482
15947
  const FLY_NF_CACHE_HEAL_FLAG = 'nf-cache-heal-attempted';
15483
15948
  /**
@@ -15496,14 +15961,8 @@ const FLY_NF_CACHE_HEAL_FLAG = 'nf-cache-heal-attempted';
15496
15961
  * sessionStorage is unavailable (caller should surface a fallback).
15497
15962
  */
15498
15963
  function healNativeFederationCacheOnce() {
15499
- try {
15500
- if (sessionStorage.getItem(FLY_NF_CACHE_HEAL_FLAG))
15501
- return false;
15502
- sessionStorage.setItem(FLY_NF_CACHE_HEAL_FLAG, '1');
15503
- }
15504
- catch {
15964
+ if (!trySpendHealAttempt(FLY_NF_CACHE_HEAL_FLAG))
15505
15965
  return false;
15506
- }
15507
15966
  const reload = () => {
15508
15967
  try {
15509
15968
  window.location.reload();
@@ -15930,6 +16389,13 @@ class FlyModerationQueueComponent {
15930
16389
  error = input(null, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
15931
16390
  /** The currently applied status filter; `null` = "All". Host-controlled, like `[sort]` on `fly-comment-thread`. */
15932
16391
  status = input(null, ...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
16392
+ /**
16393
+ * Optional id→name lookup for the reporter column, mirroring
16394
+ * `fly-comment-thread`'s input of the same name. Bind
16395
+ * `FlyUserDirectoryService.label`; absent, rows fall back to the raw id.
16396
+ * See {@link reporterLabel} for the resolution order.
16397
+ */
16398
+ resolveDisplayName = input(null, ...(ngDevMode ? [{ debugName: "resolveDisplayName" }] : /* istanbul ignore next */ []));
15933
16399
  // ── Outputs ──────────────────────────────────────────────────────────────────
15934
16400
  /** A status-filter pick (page resets to 1) or a `loadMore` (next page at the current filter). */
15935
16401
  loadPage = output();
@@ -15974,6 +16440,16 @@ class FlyModerationQueueComponent {
15974
16440
  subjectDisplay(r) {
15975
16441
  return r.subjectLabel || `${r.subjectType} #${this.shortId(r.subjectId)}`;
15976
16442
  }
16443
+ /**
16444
+ * Who filed the report. Resolution order: explicit `reporterDisplayName` →
16445
+ * {@link resolveDisplayName} → the raw `reporterUserId`. Same contract as
16446
+ * `fly-comment-thread`'s `authorLabel`.
16447
+ */
16448
+ reporterLabel(r) {
16449
+ if (r.reporterDisplayName)
16450
+ return r.reporterDisplayName;
16451
+ return this.resolveDisplayName()?.(r.reporterUserId) || r.reporterUserId;
16452
+ }
15977
16453
  formatDate(iso) {
15978
16454
  const d = new Date(iso);
15979
16455
  return Number.isNaN(d.getTime()) ? '' : d.toLocaleString();
@@ -16090,12 +16566,12 @@ class FlyModerationQueueComponent {
16090
16566
  btn?.focus({ preventScroll: true });
16091
16567
  }
16092
16568
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyModerationQueueComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
16093
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyModerationQueueComponent, isStandalone: true, selector: "fly-moderation-queue", inputs: { page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: true, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { loadPage: "loadPage", resolve: "resolve", lockThread: "lockThread", openSubject: "openSubject" }, host: { listeners: { "document:keydown.escape": "onEscape()" }, classAttribute: "fly-moderation-queue" }, ngImport: i0, template: "<div class=\"fly-moderation-queue__body\">\r\n <!-- \u2500\u2500 Header: count + status filter \u2500\u2500 -->\r\n <header class=\"fly-moderation-queue__header\">\r\n <span class=\"fly-moderation-queue__count\">\r\n {{ 'moderation.count' | translate: { n: page().total } }}\r\n </span>\r\n\r\n <div class=\"fly-moderation-queue__status-filter\" role=\"group\" [attr.aria-label]=\"'moderation.status_label' | translate\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-moderation-queue__status-btn\"\r\n [class.fly-moderation-queue__status-btn--active]=\"status() === null\"\r\n [attr.aria-pressed]=\"status() === null\"\r\n [disabled]=\"loading()\"\r\n (click)=\"onStatusPick(null)\">\r\n {{ 'moderation.status_all' | translate }}\r\n </button>\r\n @for (s of statuses; track s) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-moderation-queue__status-btn\"\r\n [class.fly-moderation-queue__status-btn--active]=\"status() === s\"\r\n [attr.aria-pressed]=\"status() === s\"\r\n [disabled]=\"loading()\"\r\n (click)=\"onStatusPick(s)\">\r\n {{ 'moderation.status.' + s | translate }}\r\n </button>\r\n }\r\n </div>\r\n </header>\r\n\r\n <!-- \u2500\u2500 States \u2500\u2500 -->\r\n @if (error()) {\r\n <p class=\"fly-moderation-queue__error\" role=\"alert\">{{ error() }}</p>\r\n } @else if (loading() && page().items.length === 0) {\r\n <p class=\"fly-moderation-queue__status-msg\">{{ 'moderation.loading' | translate }}</p>\r\n } @else if (page().items.length === 0) {\r\n <p class=\"fly-moderation-queue__empty\">{{ 'moderation.empty' | translate }}</p>\r\n } @else {\r\n <!-- \u2500\u2500 Report table \u2500\u2500 -->\r\n <div class=\"fly-moderation-queue__table-wrap\">\r\n <table class=\"fly-moderation-queue__table\">\r\n <caption class=\"fly-moderation-queue__sr-only\">{{ 'moderation.table_caption' | translate }}</caption>\r\n <thead>\r\n <tr>\r\n <th scope=\"col\">{{ 'moderation.col_reason' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_reporter' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_reported' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_excerpt' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_subject' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_status' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_actions' | translate }}</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (r of page().items; track r.id) {\r\n <tr>\r\n <td class=\"fly-moderation-queue__reason-cell\">\r\n <span class=\"fly-moderation-queue__reason\">{{ 'comment.reason.' + r.reason | translate }}</span>\r\n @if (r.detail) {\r\n <span class=\"fly-moderation-queue__detail\" [title]=\"r.detail\">{{ r.detail }}</span>\r\n }\r\n </td>\r\n <td>{{ r.reporterDisplayName || r.reporterUserId }}</td>\r\n <td>{{ formatDate(r.createdAt) }}</td>\r\n <td class=\"fly-moderation-queue__excerpt\">{{ r.commentExcerpt || '\u2014' }}</td>\r\n <td>\r\n <button type=\"button\" class=\"fly-moderation-queue__subject-link\" [title]=\"subjectHref(r)\" (click)=\"onOpenSubject(r)\">\r\n {{ subjectDisplay(r) }}\r\n </button>\r\n </td>\r\n <td>\r\n <span class=\"fly-moderation-queue__badge\" [class]=\"'fly-moderation-queue__badge--' + r.status\">\r\n {{ 'moderation.status.' + r.status | translate }}\r\n </span>\r\n @if (r.resolutionNote) {\r\n <div class=\"fly-moderation-queue__resolution-note\">{{ r.resolutionNote }}</div>\r\n }\r\n </td>\r\n <td class=\"fly-moderation-queue__actions\">\r\n @if (isResolvable(r.status)) {\r\n <button type=\"button\" class=\"fly-moderation-queue__action-btn\" [disabled]=\"loading()\" (click)=\"openResolve(r)\">\r\n {{ 'moderation.resolve' | translate }}\r\n </button>\r\n }\r\n @if (confirmingLockId() === r.id) {\r\n <span class=\"fly-moderation-queue__lock-confirm\">\r\n <button type=\"button\" class=\"fly-moderation-queue__cancel-btn\" (click)=\"cancelLock()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-moderation-queue__danger-btn\" (click)=\"confirmLock(r)\">\r\n {{ 'moderation.lock_thread' | translate }}\r\n </button>\r\n </span>\r\n } @else {\r\n <button type=\"button\" class=\"fly-moderation-queue__action-btn\" [disabled]=\"loading()\" (click)=\"requestLock(r)\">\r\n {{ 'moderation.lock_thread' | translate }}\r\n </button>\r\n }\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n\r\n @if (page().hasMore) {\r\n <button type=\"button\" class=\"fly-moderation-queue__load-more\" [disabled]=\"loading()\" (click)=\"loadMore()\">\r\n {{ 'comment.load_more' | translate }}\r\n </button>\r\n }\r\n }\r\n</div>\r\n\r\n<!-- \u2500\u2500 Resolve dialog \u2500\u2500 -->\r\n@if (resolvingReport(); as r) {\r\n <div class=\"fly-moderation-queue__overlay\">\r\n <div class=\"fly-moderation-queue__overlay-backdrop\" role=\"button\" tabindex=\"0\"\r\n [attr.aria-label]=\"'common.action.cancel' | translate\"\r\n (click)=\"closeResolve()\"\r\n (keydown.enter)=\"closeResolve()\"\r\n (keydown.space)=\"onOverlayBackdropSpace($event)\"></div>\r\n <div\r\n class=\"fly-moderation-queue__dialog\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n tabindex=\"-1\"\r\n [attr.aria-label]=\"'moderation.resolve_title' | translate\">\r\n <h3 class=\"fly-moderation-queue__dialog-title\">{{ 'moderation.resolve_title' | translate }}</h3>\r\n\r\n <fieldset class=\"fly-moderation-queue__resolution-group\">\r\n <legend>{{ 'moderation.resolution_label' | translate }}</legend>\r\n <label class=\"fly-moderation-queue__resolution-option\">\r\n <input\r\n type=\"radio\"\r\n [name]=\"resolutionGroupName\"\r\n [checked]=\"resolutionStatus() === 'ActionTaken'\"\r\n (change)=\"onResolutionStatusChange('ActionTaken')\" />\r\n <span>{{ 'moderation.resolution.ActionTaken' | translate }}</span>\r\n </label>\r\n <label class=\"fly-moderation-queue__resolution-option\">\r\n <input\r\n type=\"radio\"\r\n [name]=\"resolutionGroupName\"\r\n [checked]=\"resolutionStatus() === 'Dismissed'\"\r\n (change)=\"onResolutionStatusChange('Dismissed')\" />\r\n <span>{{ 'moderation.resolution.Dismissed' | translate }}</span>\r\n </label>\r\n </fieldset>\r\n\r\n @if (resolutionStatus() === 'ActionTaken') {\r\n <label class=\"fly-moderation-queue__checkbox-row\">\r\n <input type=\"checkbox\" [checked]=\"deleteComment()\" (change)=\"onDeleteCommentToggle($event)\" />\r\n <span>{{ 'moderation.delete_comment_label' | translate }}</span>\r\n </label>\r\n }\r\n\r\n <textarea\r\n class=\"fly-moderation-queue__textarea\"\r\n rows=\"3\"\r\n [attr.aria-label]=\"'moderation.resolution_note_label' | translate\"\r\n [placeholder]=\"'moderation.resolution_note_label' | translate\"\r\n [ngModel]=\"resolutionNote()\"\r\n (ngModelChange)=\"onResolutionNoteChange($event)\"></textarea>\r\n\r\n <div class=\"fly-moderation-queue__dialog-actions\">\r\n <button type=\"button\" class=\"fly-moderation-queue__cancel-btn\" (click)=\"closeResolve()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fly-moderation-queue__submit-btn\"\r\n [disabled]=\"!resolutionStatus()\"\r\n (click)=\"submitResolve(r)\">\r\n {{ 'moderation.resolve_submit' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--surface-card, #fff);--_surface-hover: var(--surface-hover, #f1f5f9);--_border: var(--separator, var(--surface-border, #e2e8f0));--_text: var(--label-primary, var(--text-color, #0f172a));--_text-subtle: var(--label-secondary, var(--text-color-secondary, #64748b));--_text-tertiary: var(--label-tertiary, #94a3b8);--_fill: var(--fill-tertiary, #f3f4f6);--_accent: var(--accent);--_danger: var(--system-red, #ef4444);--_blue: var(--system-blue, #0a84ff);--_orange: var(--system-orange, #ff9f0a);--_green: var(--system-green, #32d74b);--_radius: 8px;--_blue-text: #1d4ed8;--_orange-text: #9a3412;--_green-text: #166534;--_danger-text: #b91c1c;display:block;inline-size:100%;color:var(--_text);font-size:13px}.fly-moderation-queue__body{display:flex;flex-direction:column;gap:16px}.fly-moderation-queue__header{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.fly-moderation-queue__count{font-weight:700;font-size:14px}.fly-moderation-queue__status-filter{display:inline-flex;flex-wrap:wrap;border:1px solid var(--_border);border-radius:var(--_radius);overflow:hidden}.fly-moderation-queue__status-btn{padding:5px 12px;border:none;border-inline-end:1px solid var(--_border);background:transparent;color:var(--_text-subtle);font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__status-btn:last-child{border-inline-end:none}.fly-moderation-queue__status-btn:disabled{opacity:.6;cursor:not-allowed}.fly-moderation-queue__status-btn--active{background:var(--_accent);color:#fff}.fly-moderation-queue__empty,.fly-moderation-queue__status-msg{margin:0;color:var(--_text-subtle);text-align:center;padding:16px 0}.fly-moderation-queue__error{margin:0;padding:8px 12px;border-radius:var(--_radius);background:color-mix(in srgb,var(--_danger) 12%,transparent);color:var(--_danger);font-size:12px}.fly-moderation-queue__sr-only{position:absolute;inline-size:1px;block-size:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.fly-moderation-queue__table-wrap{overflow-x:auto}.fly-moderation-queue__table{inline-size:100%;border-collapse:collapse;font-size:12px}.fly-moderation-queue__table th{text-align:start;padding:6px 10px;color:var(--_text-subtle);font-weight:600;border-block-end:1px solid var(--_border);white-space:nowrap}.fly-moderation-queue__table td{padding:8px 10px;border-block-end:1px solid var(--_border);vertical-align:top}.fly-moderation-queue__reason-cell{display:table-cell}.fly-moderation-queue__reason{display:block;font-weight:600}.fly-moderation-queue__detail{display:block;margin-block-start:2px;color:var(--_text-tertiary);font-size:11px;max-inline-size:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-moderation-queue__excerpt{max-inline-size:260px;color:var(--_text-subtle);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-moderation-queue__subject-link{border:none;background:transparent;padding:0;color:var(--_accent);font:inherit;font-weight:600;cursor:pointer;text-align:start}.fly-moderation-queue__subject-link:hover{text-decoration:underline}.fly-moderation-queue__badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:700;white-space:nowrap}.fly-moderation-queue__badge--Open{background:color-mix(in srgb,var(--_blue) 12%,#fff);color:var(--_blue-text)}.fly-moderation-queue__badge--UnderReview{background:color-mix(in srgb,var(--_orange) 12%,#fff);color:var(--_orange-text)}.fly-moderation-queue__badge--ActionTaken{background:color-mix(in srgb,var(--_danger) 12%,#fff);color:var(--_danger-text)}.fly-moderation-queue__badge--Dismissed{background:color-mix(in srgb,var(--_green) 12%,#fff);color:var(--_green-text)}.fly-moderation-queue__resolution-note{margin-block-start:4px;color:var(--_text-tertiary);font-size:11px;max-inline-size:200px}.fly-moderation-queue__actions{display:flex;flex-direction:column;align-items:flex-start;gap:6px;white-space:nowrap}.fly-moderation-queue__action-btn{border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);padding:4px 10px;color:var(--_text);font:inherit;font-size:11px;font-weight:600;cursor:pointer}.fly-moderation-queue__action-btn:hover:not(:disabled){background:var(--_surface-hover)}.fly-moderation-queue__action-btn:disabled{opacity:.6;cursor:not-allowed}.fly-moderation-queue__lock-confirm{display:inline-flex;align-items:center;gap:6px}.fly-moderation-queue__load-more{align-self:flex-start;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);padding:6px 14px;color:var(--_text);font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__load-more:hover:not(:disabled){background:var(--_surface-hover)}.fly-moderation-queue__load-more:disabled{opacity:.6;cursor:not-allowed}.fly-moderation-queue__overlay{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;z-index:1000}.fly-moderation-queue__overlay-backdrop{position:absolute;inset:0;background:#00000073;border:none;padding:0;margin:0;cursor:pointer}.fly-moderation-queue__dialog{position:relative;z-index:1;inline-size:min(440px,90vw);max-block-size:85vh;overflow-y:auto;background:var(--_surface);border-radius:12px;padding:20px;display:flex;flex-direction:column;gap:14px;box-shadow:0 12px 32px #00000040}.fly-moderation-queue__dialog:focus-visible{outline:2px solid var(--_accent);outline-offset:2px}.fly-moderation-queue__dialog-title{margin:0;font-size:16px;font-weight:700}.fly-moderation-queue__resolution-group{display:flex;flex-direction:column;gap:8px;margin:0;padding:0;border:none}.fly-moderation-queue__resolution-group legend{font-weight:600;padding:0;margin-block-end:4px}.fly-moderation-queue__resolution-option,.fly-moderation-queue__checkbox-row{display:flex;align-items:center;gap:8px;cursor:pointer}.fly-moderation-queue__resolution-option input,.fly-moderation-queue__checkbox-row input{accent-color:var(--_accent);cursor:pointer}.fly-moderation-queue__textarea{inline-size:100%;padding:8px 12px;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);color:var(--_text);font:inherit;line-height:1.5;resize:vertical}.fly-moderation-queue__textarea:focus-visible{outline:2px solid var(--_accent);outline-offset:1px}.fly-moderation-queue__dialog-actions{display:flex;justify-content:flex-end;gap:8px}.fly-moderation-queue__submit-btn{padding:6px 16px;border:none;border-radius:var(--_radius);background:var(--_accent);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__submit-btn:hover:not(:disabled){opacity:.9}.fly-moderation-queue__submit-btn:disabled{opacity:.5;cursor:not-allowed}.fly-moderation-queue__cancel-btn{padding:6px 14px;border:1px solid var(--_border);border-radius:var(--_radius);background:transparent;color:var(--_text-subtle);font:inherit;cursor:pointer}.fly-moderation-queue__cancel-btn:hover{background:var(--_surface-hover)}.fly-moderation-queue__danger-btn{padding:6px 14px;border:none;border-radius:var(--_radius);background:var(--_danger);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__danger-btn:hover{opacity:.9}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
16569
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyModerationQueueComponent, isStandalone: true, selector: "fly-moderation-queue", inputs: { page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: true, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null }, resolveDisplayName: { classPropertyName: "resolveDisplayName", publicName: "resolveDisplayName", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { loadPage: "loadPage", resolve: "resolve", lockThread: "lockThread", openSubject: "openSubject" }, host: { listeners: { "document:keydown.escape": "onEscape()" }, classAttribute: "fly-moderation-queue" }, ngImport: i0, template: "<div class=\"fly-moderation-queue__body\">\r\n <!-- \u2500\u2500 Header: count + status filter \u2500\u2500 -->\r\n <header class=\"fly-moderation-queue__header\">\r\n <span class=\"fly-moderation-queue__count\">\r\n {{ 'moderation.count' | translate: { n: page().total } }}\r\n </span>\r\n\r\n <div class=\"fly-moderation-queue__status-filter\" role=\"group\" [attr.aria-label]=\"'moderation.status_label' | translate\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-moderation-queue__status-btn\"\r\n [class.fly-moderation-queue__status-btn--active]=\"status() === null\"\r\n [attr.aria-pressed]=\"status() === null\"\r\n [disabled]=\"loading()\"\r\n (click)=\"onStatusPick(null)\">\r\n {{ 'moderation.status_all' | translate }}\r\n </button>\r\n @for (s of statuses; track s) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-moderation-queue__status-btn\"\r\n [class.fly-moderation-queue__status-btn--active]=\"status() === s\"\r\n [attr.aria-pressed]=\"status() === s\"\r\n [disabled]=\"loading()\"\r\n (click)=\"onStatusPick(s)\">\r\n {{ 'moderation.status.' + s | translate }}\r\n </button>\r\n }\r\n </div>\r\n </header>\r\n\r\n <!-- \u2500\u2500 States \u2500\u2500 -->\r\n @if (error()) {\r\n <p class=\"fly-moderation-queue__error\" role=\"alert\">{{ error() }}</p>\r\n } @else if (loading() && page().items.length === 0) {\r\n <p class=\"fly-moderation-queue__status-msg\">{{ 'moderation.loading' | translate }}</p>\r\n } @else if (page().items.length === 0) {\r\n <p class=\"fly-moderation-queue__empty\">{{ 'moderation.empty' | translate }}</p>\r\n } @else {\r\n <!-- \u2500\u2500 Report table \u2500\u2500 -->\r\n <div class=\"fly-moderation-queue__table-wrap\">\r\n <table class=\"fly-moderation-queue__table\">\r\n <caption class=\"fly-moderation-queue__sr-only\">{{ 'moderation.table_caption' | translate }}</caption>\r\n <thead>\r\n <tr>\r\n <th scope=\"col\">{{ 'moderation.col_reason' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_reporter' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_reported' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_excerpt' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_subject' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_status' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_actions' | translate }}</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (r of page().items; track r.id) {\r\n <tr>\r\n <td class=\"fly-moderation-queue__reason-cell\">\r\n <span class=\"fly-moderation-queue__reason\">{{ 'comment.reason.' + r.reason | translate }}</span>\r\n @if (r.detail) {\r\n <span class=\"fly-moderation-queue__detail\" [title]=\"r.detail\">{{ r.detail }}</span>\r\n }\r\n </td>\r\n <td>{{ reporterLabel(r) }}</td>\r\n <td>{{ formatDate(r.createdAt) }}</td>\r\n <td class=\"fly-moderation-queue__excerpt\">{{ r.commentExcerpt || '\u2014' }}</td>\r\n <td>\r\n <button type=\"button\" class=\"fly-moderation-queue__subject-link\" [title]=\"subjectHref(r)\" (click)=\"onOpenSubject(r)\">\r\n {{ subjectDisplay(r) }}\r\n </button>\r\n </td>\r\n <td>\r\n <span class=\"fly-moderation-queue__badge\" [class]=\"'fly-moderation-queue__badge--' + r.status\">\r\n {{ 'moderation.status.' + r.status | translate }}\r\n </span>\r\n @if (r.resolutionNote) {\r\n <div class=\"fly-moderation-queue__resolution-note\">{{ r.resolutionNote }}</div>\r\n }\r\n </td>\r\n <td class=\"fly-moderation-queue__actions\">\r\n @if (isResolvable(r.status)) {\r\n <button type=\"button\" class=\"fly-moderation-queue__action-btn\" [disabled]=\"loading()\" (click)=\"openResolve(r)\">\r\n {{ 'moderation.resolve' | translate }}\r\n </button>\r\n }\r\n @if (confirmingLockId() === r.id) {\r\n <span class=\"fly-moderation-queue__lock-confirm\">\r\n <button type=\"button\" class=\"fly-moderation-queue__cancel-btn\" (click)=\"cancelLock()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-moderation-queue__danger-btn\" (click)=\"confirmLock(r)\">\r\n {{ 'moderation.lock_thread' | translate }}\r\n </button>\r\n </span>\r\n } @else {\r\n <button type=\"button\" class=\"fly-moderation-queue__action-btn\" [disabled]=\"loading()\" (click)=\"requestLock(r)\">\r\n {{ 'moderation.lock_thread' | translate }}\r\n </button>\r\n }\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n\r\n @if (page().hasMore) {\r\n <button type=\"button\" class=\"fly-moderation-queue__load-more\" [disabled]=\"loading()\" (click)=\"loadMore()\">\r\n {{ 'comment.load_more' | translate }}\r\n </button>\r\n }\r\n }\r\n</div>\r\n\r\n<!-- \u2500\u2500 Resolve dialog \u2500\u2500 -->\r\n@if (resolvingReport(); as r) {\r\n <div class=\"fly-moderation-queue__overlay\">\r\n <div class=\"fly-moderation-queue__overlay-backdrop\" role=\"button\" tabindex=\"0\"\r\n [attr.aria-label]=\"'common.action.cancel' | translate\"\r\n (click)=\"closeResolve()\"\r\n (keydown.enter)=\"closeResolve()\"\r\n (keydown.space)=\"onOverlayBackdropSpace($event)\"></div>\r\n <div\r\n class=\"fly-moderation-queue__dialog\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n tabindex=\"-1\"\r\n [attr.aria-label]=\"'moderation.resolve_title' | translate\">\r\n <h3 class=\"fly-moderation-queue__dialog-title\">{{ 'moderation.resolve_title' | translate }}</h3>\r\n\r\n <fieldset class=\"fly-moderation-queue__resolution-group\">\r\n <legend>{{ 'moderation.resolution_label' | translate }}</legend>\r\n <label class=\"fly-moderation-queue__resolution-option\">\r\n <input\r\n type=\"radio\"\r\n [name]=\"resolutionGroupName\"\r\n [checked]=\"resolutionStatus() === 'ActionTaken'\"\r\n (change)=\"onResolutionStatusChange('ActionTaken')\" />\r\n <span>{{ 'moderation.resolution.ActionTaken' | translate }}</span>\r\n </label>\r\n <label class=\"fly-moderation-queue__resolution-option\">\r\n <input\r\n type=\"radio\"\r\n [name]=\"resolutionGroupName\"\r\n [checked]=\"resolutionStatus() === 'Dismissed'\"\r\n (change)=\"onResolutionStatusChange('Dismissed')\" />\r\n <span>{{ 'moderation.resolution.Dismissed' | translate }}</span>\r\n </label>\r\n </fieldset>\r\n\r\n @if (resolutionStatus() === 'ActionTaken') {\r\n <label class=\"fly-moderation-queue__checkbox-row\">\r\n <input type=\"checkbox\" [checked]=\"deleteComment()\" (change)=\"onDeleteCommentToggle($event)\" />\r\n <span>{{ 'moderation.delete_comment_label' | translate }}</span>\r\n </label>\r\n }\r\n\r\n <textarea\r\n class=\"fly-moderation-queue__textarea\"\r\n rows=\"3\"\r\n [attr.aria-label]=\"'moderation.resolution_note_label' | translate\"\r\n [placeholder]=\"'moderation.resolution_note_label' | translate\"\r\n [ngModel]=\"resolutionNote()\"\r\n (ngModelChange)=\"onResolutionNoteChange($event)\"></textarea>\r\n\r\n <div class=\"fly-moderation-queue__dialog-actions\">\r\n <button type=\"button\" class=\"fly-moderation-queue__cancel-btn\" (click)=\"closeResolve()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fly-moderation-queue__submit-btn\"\r\n [disabled]=\"!resolutionStatus()\"\r\n (click)=\"submitResolve(r)\">\r\n {{ 'moderation.resolve_submit' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--surface-card, #fff);--_surface-hover: var(--surface-hover, #f1f5f9);--_border: var(--separator, var(--surface-border, #e2e8f0));--_text: var(--label-primary, var(--text-color, #0f172a));--_text-subtle: var(--label-secondary, var(--text-color-secondary, #64748b));--_text-tertiary: var(--label-tertiary, #94a3b8);--_fill: var(--fill-tertiary, #f3f4f6);--_accent: var(--accent);--_danger: var(--system-red, #ef4444);--_blue: var(--system-blue, #0a84ff);--_orange: var(--system-orange, #ff9f0a);--_green: var(--system-green, #32d74b);--_radius: 8px;--_blue-text: #1d4ed8;--_orange-text: #9a3412;--_green-text: #166534;--_danger-text: #b91c1c;display:block;inline-size:100%;color:var(--_text);font-size:13px}.fly-moderation-queue__body{display:flex;flex-direction:column;gap:16px}.fly-moderation-queue__header{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.fly-moderation-queue__count{font-weight:700;font-size:14px}.fly-moderation-queue__status-filter{display:inline-flex;flex-wrap:wrap;border:1px solid var(--_border);border-radius:var(--_radius);overflow:hidden}.fly-moderation-queue__status-btn{padding:5px 12px;border:none;border-inline-end:1px solid var(--_border);background:transparent;color:var(--_text-subtle);font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__status-btn:last-child{border-inline-end:none}.fly-moderation-queue__status-btn:disabled{opacity:.6;cursor:not-allowed}.fly-moderation-queue__status-btn--active{background:var(--_accent);color:#fff}.fly-moderation-queue__empty,.fly-moderation-queue__status-msg{margin:0;color:var(--_text-subtle);text-align:center;padding:16px 0}.fly-moderation-queue__error{margin:0;padding:8px 12px;border-radius:var(--_radius);background:color-mix(in srgb,var(--_danger) 12%,transparent);color:var(--_danger);font-size:12px}.fly-moderation-queue__sr-only{position:absolute;inline-size:1px;block-size:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.fly-moderation-queue__table-wrap{overflow-x:auto}.fly-moderation-queue__table{inline-size:100%;border-collapse:collapse;font-size:12px}.fly-moderation-queue__table th{text-align:start;padding:6px 10px;color:var(--_text-subtle);font-weight:600;border-block-end:1px solid var(--_border);white-space:nowrap}.fly-moderation-queue__table td{padding:8px 10px;border-block-end:1px solid var(--_border);vertical-align:top}.fly-moderation-queue__reason-cell{display:table-cell}.fly-moderation-queue__reason{display:block;font-weight:600}.fly-moderation-queue__detail{display:block;margin-block-start:2px;color:var(--_text-tertiary);font-size:11px;max-inline-size:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-moderation-queue__excerpt{max-inline-size:260px;color:var(--_text-subtle);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-moderation-queue__subject-link{border:none;background:transparent;padding:0;color:var(--_accent);font:inherit;font-weight:600;cursor:pointer;text-align:start}.fly-moderation-queue__subject-link:hover{text-decoration:underline}.fly-moderation-queue__badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:700;white-space:nowrap}.fly-moderation-queue__badge--Open{background:color-mix(in srgb,var(--_blue) 12%,#fff);color:var(--_blue-text)}.fly-moderation-queue__badge--UnderReview{background:color-mix(in srgb,var(--_orange) 12%,#fff);color:var(--_orange-text)}.fly-moderation-queue__badge--ActionTaken{background:color-mix(in srgb,var(--_danger) 12%,#fff);color:var(--_danger-text)}.fly-moderation-queue__badge--Dismissed{background:color-mix(in srgb,var(--_green) 12%,#fff);color:var(--_green-text)}.fly-moderation-queue__resolution-note{margin-block-start:4px;color:var(--_text-tertiary);font-size:11px;max-inline-size:200px}.fly-moderation-queue__actions{display:flex;flex-direction:column;align-items:flex-start;gap:6px;white-space:nowrap}.fly-moderation-queue__action-btn{border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);padding:4px 10px;color:var(--_text);font:inherit;font-size:11px;font-weight:600;cursor:pointer}.fly-moderation-queue__action-btn:hover:not(:disabled){background:var(--_surface-hover)}.fly-moderation-queue__action-btn:disabled{opacity:.6;cursor:not-allowed}.fly-moderation-queue__lock-confirm{display:inline-flex;align-items:center;gap:6px}.fly-moderation-queue__load-more{align-self:flex-start;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);padding:6px 14px;color:var(--_text);font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__load-more:hover:not(:disabled){background:var(--_surface-hover)}.fly-moderation-queue__load-more:disabled{opacity:.6;cursor:not-allowed}.fly-moderation-queue__overlay{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;z-index:1000}.fly-moderation-queue__overlay-backdrop{position:absolute;inset:0;background:#00000073;border:none;padding:0;margin:0;cursor:pointer}.fly-moderation-queue__dialog{position:relative;z-index:1;inline-size:min(440px,90vw);max-block-size:85vh;overflow-y:auto;background:var(--_surface);border-radius:12px;padding:20px;display:flex;flex-direction:column;gap:14px;box-shadow:0 12px 32px #00000040}.fly-moderation-queue__dialog:focus-visible{outline:2px solid var(--_accent);outline-offset:2px}.fly-moderation-queue__dialog-title{margin:0;font-size:16px;font-weight:700}.fly-moderation-queue__resolution-group{display:flex;flex-direction:column;gap:8px;margin:0;padding:0;border:none}.fly-moderation-queue__resolution-group legend{font-weight:600;padding:0;margin-block-end:4px}.fly-moderation-queue__resolution-option,.fly-moderation-queue__checkbox-row{display:flex;align-items:center;gap:8px;cursor:pointer}.fly-moderation-queue__resolution-option input,.fly-moderation-queue__checkbox-row input{accent-color:var(--_accent);cursor:pointer}.fly-moderation-queue__textarea{inline-size:100%;padding:8px 12px;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);color:var(--_text);font:inherit;line-height:1.5;resize:vertical}.fly-moderation-queue__textarea:focus-visible{outline:2px solid var(--_accent);outline-offset:1px}.fly-moderation-queue__dialog-actions{display:flex;justify-content:flex-end;gap:8px}.fly-moderation-queue__submit-btn{padding:6px 16px;border:none;border-radius:var(--_radius);background:var(--_accent);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__submit-btn:hover:not(:disabled){opacity:.9}.fly-moderation-queue__submit-btn:disabled{opacity:.5;cursor:not-allowed}.fly-moderation-queue__cancel-btn{padding:6px 14px;border:1px solid var(--_border);border-radius:var(--_radius);background:transparent;color:var(--_text-subtle);font:inherit;cursor:pointer}.fly-moderation-queue__cancel-btn:hover{background:var(--_surface-hover)}.fly-moderation-queue__danger-btn{padding:6px 14px;border:none;border-radius:var(--_radius);background:var(--_danger);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__danger-btn:hover{opacity:.9}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
16094
16570
  }
16095
16571
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyModerationQueueComponent, decorators: [{
16096
16572
  type: Component,
16097
- args: [{ selector: 'fly-moderation-queue', standalone: true, imports: [CommonModule, FormsModule, TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'fly-moderation-queue' }, template: "<div class=\"fly-moderation-queue__body\">\r\n <!-- \u2500\u2500 Header: count + status filter \u2500\u2500 -->\r\n <header class=\"fly-moderation-queue__header\">\r\n <span class=\"fly-moderation-queue__count\">\r\n {{ 'moderation.count' | translate: { n: page().total } }}\r\n </span>\r\n\r\n <div class=\"fly-moderation-queue__status-filter\" role=\"group\" [attr.aria-label]=\"'moderation.status_label' | translate\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-moderation-queue__status-btn\"\r\n [class.fly-moderation-queue__status-btn--active]=\"status() === null\"\r\n [attr.aria-pressed]=\"status() === null\"\r\n [disabled]=\"loading()\"\r\n (click)=\"onStatusPick(null)\">\r\n {{ 'moderation.status_all' | translate }}\r\n </button>\r\n @for (s of statuses; track s) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-moderation-queue__status-btn\"\r\n [class.fly-moderation-queue__status-btn--active]=\"status() === s\"\r\n [attr.aria-pressed]=\"status() === s\"\r\n [disabled]=\"loading()\"\r\n (click)=\"onStatusPick(s)\">\r\n {{ 'moderation.status.' + s | translate }}\r\n </button>\r\n }\r\n </div>\r\n </header>\r\n\r\n <!-- \u2500\u2500 States \u2500\u2500 -->\r\n @if (error()) {\r\n <p class=\"fly-moderation-queue__error\" role=\"alert\">{{ error() }}</p>\r\n } @else if (loading() && page().items.length === 0) {\r\n <p class=\"fly-moderation-queue__status-msg\">{{ 'moderation.loading' | translate }}</p>\r\n } @else if (page().items.length === 0) {\r\n <p class=\"fly-moderation-queue__empty\">{{ 'moderation.empty' | translate }}</p>\r\n } @else {\r\n <!-- \u2500\u2500 Report table \u2500\u2500 -->\r\n <div class=\"fly-moderation-queue__table-wrap\">\r\n <table class=\"fly-moderation-queue__table\">\r\n <caption class=\"fly-moderation-queue__sr-only\">{{ 'moderation.table_caption' | translate }}</caption>\r\n <thead>\r\n <tr>\r\n <th scope=\"col\">{{ 'moderation.col_reason' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_reporter' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_reported' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_excerpt' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_subject' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_status' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_actions' | translate }}</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (r of page().items; track r.id) {\r\n <tr>\r\n <td class=\"fly-moderation-queue__reason-cell\">\r\n <span class=\"fly-moderation-queue__reason\">{{ 'comment.reason.' + r.reason | translate }}</span>\r\n @if (r.detail) {\r\n <span class=\"fly-moderation-queue__detail\" [title]=\"r.detail\">{{ r.detail }}</span>\r\n }\r\n </td>\r\n <td>{{ r.reporterDisplayName || r.reporterUserId }}</td>\r\n <td>{{ formatDate(r.createdAt) }}</td>\r\n <td class=\"fly-moderation-queue__excerpt\">{{ r.commentExcerpt || '\u2014' }}</td>\r\n <td>\r\n <button type=\"button\" class=\"fly-moderation-queue__subject-link\" [title]=\"subjectHref(r)\" (click)=\"onOpenSubject(r)\">\r\n {{ subjectDisplay(r) }}\r\n </button>\r\n </td>\r\n <td>\r\n <span class=\"fly-moderation-queue__badge\" [class]=\"'fly-moderation-queue__badge--' + r.status\">\r\n {{ 'moderation.status.' + r.status | translate }}\r\n </span>\r\n @if (r.resolutionNote) {\r\n <div class=\"fly-moderation-queue__resolution-note\">{{ r.resolutionNote }}</div>\r\n }\r\n </td>\r\n <td class=\"fly-moderation-queue__actions\">\r\n @if (isResolvable(r.status)) {\r\n <button type=\"button\" class=\"fly-moderation-queue__action-btn\" [disabled]=\"loading()\" (click)=\"openResolve(r)\">\r\n {{ 'moderation.resolve' | translate }}\r\n </button>\r\n }\r\n @if (confirmingLockId() === r.id) {\r\n <span class=\"fly-moderation-queue__lock-confirm\">\r\n <button type=\"button\" class=\"fly-moderation-queue__cancel-btn\" (click)=\"cancelLock()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-moderation-queue__danger-btn\" (click)=\"confirmLock(r)\">\r\n {{ 'moderation.lock_thread' | translate }}\r\n </button>\r\n </span>\r\n } @else {\r\n <button type=\"button\" class=\"fly-moderation-queue__action-btn\" [disabled]=\"loading()\" (click)=\"requestLock(r)\">\r\n {{ 'moderation.lock_thread' | translate }}\r\n </button>\r\n }\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n\r\n @if (page().hasMore) {\r\n <button type=\"button\" class=\"fly-moderation-queue__load-more\" [disabled]=\"loading()\" (click)=\"loadMore()\">\r\n {{ 'comment.load_more' | translate }}\r\n </button>\r\n }\r\n }\r\n</div>\r\n\r\n<!-- \u2500\u2500 Resolve dialog \u2500\u2500 -->\r\n@if (resolvingReport(); as r) {\r\n <div class=\"fly-moderation-queue__overlay\">\r\n <div class=\"fly-moderation-queue__overlay-backdrop\" role=\"button\" tabindex=\"0\"\r\n [attr.aria-label]=\"'common.action.cancel' | translate\"\r\n (click)=\"closeResolve()\"\r\n (keydown.enter)=\"closeResolve()\"\r\n (keydown.space)=\"onOverlayBackdropSpace($event)\"></div>\r\n <div\r\n class=\"fly-moderation-queue__dialog\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n tabindex=\"-1\"\r\n [attr.aria-label]=\"'moderation.resolve_title' | translate\">\r\n <h3 class=\"fly-moderation-queue__dialog-title\">{{ 'moderation.resolve_title' | translate }}</h3>\r\n\r\n <fieldset class=\"fly-moderation-queue__resolution-group\">\r\n <legend>{{ 'moderation.resolution_label' | translate }}</legend>\r\n <label class=\"fly-moderation-queue__resolution-option\">\r\n <input\r\n type=\"radio\"\r\n [name]=\"resolutionGroupName\"\r\n [checked]=\"resolutionStatus() === 'ActionTaken'\"\r\n (change)=\"onResolutionStatusChange('ActionTaken')\" />\r\n <span>{{ 'moderation.resolution.ActionTaken' | translate }}</span>\r\n </label>\r\n <label class=\"fly-moderation-queue__resolution-option\">\r\n <input\r\n type=\"radio\"\r\n [name]=\"resolutionGroupName\"\r\n [checked]=\"resolutionStatus() === 'Dismissed'\"\r\n (change)=\"onResolutionStatusChange('Dismissed')\" />\r\n <span>{{ 'moderation.resolution.Dismissed' | translate }}</span>\r\n </label>\r\n </fieldset>\r\n\r\n @if (resolutionStatus() === 'ActionTaken') {\r\n <label class=\"fly-moderation-queue__checkbox-row\">\r\n <input type=\"checkbox\" [checked]=\"deleteComment()\" (change)=\"onDeleteCommentToggle($event)\" />\r\n <span>{{ 'moderation.delete_comment_label' | translate }}</span>\r\n </label>\r\n }\r\n\r\n <textarea\r\n class=\"fly-moderation-queue__textarea\"\r\n rows=\"3\"\r\n [attr.aria-label]=\"'moderation.resolution_note_label' | translate\"\r\n [placeholder]=\"'moderation.resolution_note_label' | translate\"\r\n [ngModel]=\"resolutionNote()\"\r\n (ngModelChange)=\"onResolutionNoteChange($event)\"></textarea>\r\n\r\n <div class=\"fly-moderation-queue__dialog-actions\">\r\n <button type=\"button\" class=\"fly-moderation-queue__cancel-btn\" (click)=\"closeResolve()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fly-moderation-queue__submit-btn\"\r\n [disabled]=\"!resolutionStatus()\"\r\n (click)=\"submitResolve(r)\">\r\n {{ 'moderation.resolve_submit' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--surface-card, #fff);--_surface-hover: var(--surface-hover, #f1f5f9);--_border: var(--separator, var(--surface-border, #e2e8f0));--_text: var(--label-primary, var(--text-color, #0f172a));--_text-subtle: var(--label-secondary, var(--text-color-secondary, #64748b));--_text-tertiary: var(--label-tertiary, #94a3b8);--_fill: var(--fill-tertiary, #f3f4f6);--_accent: var(--accent);--_danger: var(--system-red, #ef4444);--_blue: var(--system-blue, #0a84ff);--_orange: var(--system-orange, #ff9f0a);--_green: var(--system-green, #32d74b);--_radius: 8px;--_blue-text: #1d4ed8;--_orange-text: #9a3412;--_green-text: #166534;--_danger-text: #b91c1c;display:block;inline-size:100%;color:var(--_text);font-size:13px}.fly-moderation-queue__body{display:flex;flex-direction:column;gap:16px}.fly-moderation-queue__header{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.fly-moderation-queue__count{font-weight:700;font-size:14px}.fly-moderation-queue__status-filter{display:inline-flex;flex-wrap:wrap;border:1px solid var(--_border);border-radius:var(--_radius);overflow:hidden}.fly-moderation-queue__status-btn{padding:5px 12px;border:none;border-inline-end:1px solid var(--_border);background:transparent;color:var(--_text-subtle);font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__status-btn:last-child{border-inline-end:none}.fly-moderation-queue__status-btn:disabled{opacity:.6;cursor:not-allowed}.fly-moderation-queue__status-btn--active{background:var(--_accent);color:#fff}.fly-moderation-queue__empty,.fly-moderation-queue__status-msg{margin:0;color:var(--_text-subtle);text-align:center;padding:16px 0}.fly-moderation-queue__error{margin:0;padding:8px 12px;border-radius:var(--_radius);background:color-mix(in srgb,var(--_danger) 12%,transparent);color:var(--_danger);font-size:12px}.fly-moderation-queue__sr-only{position:absolute;inline-size:1px;block-size:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.fly-moderation-queue__table-wrap{overflow-x:auto}.fly-moderation-queue__table{inline-size:100%;border-collapse:collapse;font-size:12px}.fly-moderation-queue__table th{text-align:start;padding:6px 10px;color:var(--_text-subtle);font-weight:600;border-block-end:1px solid var(--_border);white-space:nowrap}.fly-moderation-queue__table td{padding:8px 10px;border-block-end:1px solid var(--_border);vertical-align:top}.fly-moderation-queue__reason-cell{display:table-cell}.fly-moderation-queue__reason{display:block;font-weight:600}.fly-moderation-queue__detail{display:block;margin-block-start:2px;color:var(--_text-tertiary);font-size:11px;max-inline-size:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-moderation-queue__excerpt{max-inline-size:260px;color:var(--_text-subtle);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-moderation-queue__subject-link{border:none;background:transparent;padding:0;color:var(--_accent);font:inherit;font-weight:600;cursor:pointer;text-align:start}.fly-moderation-queue__subject-link:hover{text-decoration:underline}.fly-moderation-queue__badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:700;white-space:nowrap}.fly-moderation-queue__badge--Open{background:color-mix(in srgb,var(--_blue) 12%,#fff);color:var(--_blue-text)}.fly-moderation-queue__badge--UnderReview{background:color-mix(in srgb,var(--_orange) 12%,#fff);color:var(--_orange-text)}.fly-moderation-queue__badge--ActionTaken{background:color-mix(in srgb,var(--_danger) 12%,#fff);color:var(--_danger-text)}.fly-moderation-queue__badge--Dismissed{background:color-mix(in srgb,var(--_green) 12%,#fff);color:var(--_green-text)}.fly-moderation-queue__resolution-note{margin-block-start:4px;color:var(--_text-tertiary);font-size:11px;max-inline-size:200px}.fly-moderation-queue__actions{display:flex;flex-direction:column;align-items:flex-start;gap:6px;white-space:nowrap}.fly-moderation-queue__action-btn{border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);padding:4px 10px;color:var(--_text);font:inherit;font-size:11px;font-weight:600;cursor:pointer}.fly-moderation-queue__action-btn:hover:not(:disabled){background:var(--_surface-hover)}.fly-moderation-queue__action-btn:disabled{opacity:.6;cursor:not-allowed}.fly-moderation-queue__lock-confirm{display:inline-flex;align-items:center;gap:6px}.fly-moderation-queue__load-more{align-self:flex-start;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);padding:6px 14px;color:var(--_text);font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__load-more:hover:not(:disabled){background:var(--_surface-hover)}.fly-moderation-queue__load-more:disabled{opacity:.6;cursor:not-allowed}.fly-moderation-queue__overlay{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;z-index:1000}.fly-moderation-queue__overlay-backdrop{position:absolute;inset:0;background:#00000073;border:none;padding:0;margin:0;cursor:pointer}.fly-moderation-queue__dialog{position:relative;z-index:1;inline-size:min(440px,90vw);max-block-size:85vh;overflow-y:auto;background:var(--_surface);border-radius:12px;padding:20px;display:flex;flex-direction:column;gap:14px;box-shadow:0 12px 32px #00000040}.fly-moderation-queue__dialog:focus-visible{outline:2px solid var(--_accent);outline-offset:2px}.fly-moderation-queue__dialog-title{margin:0;font-size:16px;font-weight:700}.fly-moderation-queue__resolution-group{display:flex;flex-direction:column;gap:8px;margin:0;padding:0;border:none}.fly-moderation-queue__resolution-group legend{font-weight:600;padding:0;margin-block-end:4px}.fly-moderation-queue__resolution-option,.fly-moderation-queue__checkbox-row{display:flex;align-items:center;gap:8px;cursor:pointer}.fly-moderation-queue__resolution-option input,.fly-moderation-queue__checkbox-row input{accent-color:var(--_accent);cursor:pointer}.fly-moderation-queue__textarea{inline-size:100%;padding:8px 12px;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);color:var(--_text);font:inherit;line-height:1.5;resize:vertical}.fly-moderation-queue__textarea:focus-visible{outline:2px solid var(--_accent);outline-offset:1px}.fly-moderation-queue__dialog-actions{display:flex;justify-content:flex-end;gap:8px}.fly-moderation-queue__submit-btn{padding:6px 16px;border:none;border-radius:var(--_radius);background:var(--_accent);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__submit-btn:hover:not(:disabled){opacity:.9}.fly-moderation-queue__submit-btn:disabled{opacity:.5;cursor:not-allowed}.fly-moderation-queue__cancel-btn{padding:6px 14px;border:1px solid var(--_border);border-radius:var(--_radius);background:transparent;color:var(--_text-subtle);font:inherit;cursor:pointer}.fly-moderation-queue__cancel-btn:hover{background:var(--_surface-hover)}.fly-moderation-queue__danger-btn{padding:6px 14px;border:none;border-radius:var(--_radius);background:var(--_danger);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__danger-btn:hover{opacity:.9}\n"] }]
16098
- }], propDecorators: { page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: true }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: false }] }], loadPage: [{ type: i0.Output, args: ["loadPage"] }], resolve: [{ type: i0.Output, args: ["resolve"] }], lockThread: [{ type: i0.Output, args: ["lockThread"] }], openSubject: [{ type: i0.Output, args: ["openSubject"] }], onEscape: [{
16573
+ args: [{ selector: 'fly-moderation-queue', standalone: true, imports: [CommonModule, FormsModule, TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'fly-moderation-queue' }, template: "<div class=\"fly-moderation-queue__body\">\r\n <!-- \u2500\u2500 Header: count + status filter \u2500\u2500 -->\r\n <header class=\"fly-moderation-queue__header\">\r\n <span class=\"fly-moderation-queue__count\">\r\n {{ 'moderation.count' | translate: { n: page().total } }}\r\n </span>\r\n\r\n <div class=\"fly-moderation-queue__status-filter\" role=\"group\" [attr.aria-label]=\"'moderation.status_label' | translate\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-moderation-queue__status-btn\"\r\n [class.fly-moderation-queue__status-btn--active]=\"status() === null\"\r\n [attr.aria-pressed]=\"status() === null\"\r\n [disabled]=\"loading()\"\r\n (click)=\"onStatusPick(null)\">\r\n {{ 'moderation.status_all' | translate }}\r\n </button>\r\n @for (s of statuses; track s) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-moderation-queue__status-btn\"\r\n [class.fly-moderation-queue__status-btn--active]=\"status() === s\"\r\n [attr.aria-pressed]=\"status() === s\"\r\n [disabled]=\"loading()\"\r\n (click)=\"onStatusPick(s)\">\r\n {{ 'moderation.status.' + s | translate }}\r\n </button>\r\n }\r\n </div>\r\n </header>\r\n\r\n <!-- \u2500\u2500 States \u2500\u2500 -->\r\n @if (error()) {\r\n <p class=\"fly-moderation-queue__error\" role=\"alert\">{{ error() }}</p>\r\n } @else if (loading() && page().items.length === 0) {\r\n <p class=\"fly-moderation-queue__status-msg\">{{ 'moderation.loading' | translate }}</p>\r\n } @else if (page().items.length === 0) {\r\n <p class=\"fly-moderation-queue__empty\">{{ 'moderation.empty' | translate }}</p>\r\n } @else {\r\n <!-- \u2500\u2500 Report table \u2500\u2500 -->\r\n <div class=\"fly-moderation-queue__table-wrap\">\r\n <table class=\"fly-moderation-queue__table\">\r\n <caption class=\"fly-moderation-queue__sr-only\">{{ 'moderation.table_caption' | translate }}</caption>\r\n <thead>\r\n <tr>\r\n <th scope=\"col\">{{ 'moderation.col_reason' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_reporter' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_reported' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_excerpt' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_subject' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_status' | translate }}</th>\r\n <th scope=\"col\">{{ 'moderation.col_actions' | translate }}</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (r of page().items; track r.id) {\r\n <tr>\r\n <td class=\"fly-moderation-queue__reason-cell\">\r\n <span class=\"fly-moderation-queue__reason\">{{ 'comment.reason.' + r.reason | translate }}</span>\r\n @if (r.detail) {\r\n <span class=\"fly-moderation-queue__detail\" [title]=\"r.detail\">{{ r.detail }}</span>\r\n }\r\n </td>\r\n <td>{{ reporterLabel(r) }}</td>\r\n <td>{{ formatDate(r.createdAt) }}</td>\r\n <td class=\"fly-moderation-queue__excerpt\">{{ r.commentExcerpt || '\u2014' }}</td>\r\n <td>\r\n <button type=\"button\" class=\"fly-moderation-queue__subject-link\" [title]=\"subjectHref(r)\" (click)=\"onOpenSubject(r)\">\r\n {{ subjectDisplay(r) }}\r\n </button>\r\n </td>\r\n <td>\r\n <span class=\"fly-moderation-queue__badge\" [class]=\"'fly-moderation-queue__badge--' + r.status\">\r\n {{ 'moderation.status.' + r.status | translate }}\r\n </span>\r\n @if (r.resolutionNote) {\r\n <div class=\"fly-moderation-queue__resolution-note\">{{ r.resolutionNote }}</div>\r\n }\r\n </td>\r\n <td class=\"fly-moderation-queue__actions\">\r\n @if (isResolvable(r.status)) {\r\n <button type=\"button\" class=\"fly-moderation-queue__action-btn\" [disabled]=\"loading()\" (click)=\"openResolve(r)\">\r\n {{ 'moderation.resolve' | translate }}\r\n </button>\r\n }\r\n @if (confirmingLockId() === r.id) {\r\n <span class=\"fly-moderation-queue__lock-confirm\">\r\n <button type=\"button\" class=\"fly-moderation-queue__cancel-btn\" (click)=\"cancelLock()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button type=\"button\" class=\"fly-moderation-queue__danger-btn\" (click)=\"confirmLock(r)\">\r\n {{ 'moderation.lock_thread' | translate }}\r\n </button>\r\n </span>\r\n } @else {\r\n <button type=\"button\" class=\"fly-moderation-queue__action-btn\" [disabled]=\"loading()\" (click)=\"requestLock(r)\">\r\n {{ 'moderation.lock_thread' | translate }}\r\n </button>\r\n }\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n\r\n @if (page().hasMore) {\r\n <button type=\"button\" class=\"fly-moderation-queue__load-more\" [disabled]=\"loading()\" (click)=\"loadMore()\">\r\n {{ 'comment.load_more' | translate }}\r\n </button>\r\n }\r\n }\r\n</div>\r\n\r\n<!-- \u2500\u2500 Resolve dialog \u2500\u2500 -->\r\n@if (resolvingReport(); as r) {\r\n <div class=\"fly-moderation-queue__overlay\">\r\n <div class=\"fly-moderation-queue__overlay-backdrop\" role=\"button\" tabindex=\"0\"\r\n [attr.aria-label]=\"'common.action.cancel' | translate\"\r\n (click)=\"closeResolve()\"\r\n (keydown.enter)=\"closeResolve()\"\r\n (keydown.space)=\"onOverlayBackdropSpace($event)\"></div>\r\n <div\r\n class=\"fly-moderation-queue__dialog\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n tabindex=\"-1\"\r\n [attr.aria-label]=\"'moderation.resolve_title' | translate\">\r\n <h3 class=\"fly-moderation-queue__dialog-title\">{{ 'moderation.resolve_title' | translate }}</h3>\r\n\r\n <fieldset class=\"fly-moderation-queue__resolution-group\">\r\n <legend>{{ 'moderation.resolution_label' | translate }}</legend>\r\n <label class=\"fly-moderation-queue__resolution-option\">\r\n <input\r\n type=\"radio\"\r\n [name]=\"resolutionGroupName\"\r\n [checked]=\"resolutionStatus() === 'ActionTaken'\"\r\n (change)=\"onResolutionStatusChange('ActionTaken')\" />\r\n <span>{{ 'moderation.resolution.ActionTaken' | translate }}</span>\r\n </label>\r\n <label class=\"fly-moderation-queue__resolution-option\">\r\n <input\r\n type=\"radio\"\r\n [name]=\"resolutionGroupName\"\r\n [checked]=\"resolutionStatus() === 'Dismissed'\"\r\n (change)=\"onResolutionStatusChange('Dismissed')\" />\r\n <span>{{ 'moderation.resolution.Dismissed' | translate }}</span>\r\n </label>\r\n </fieldset>\r\n\r\n @if (resolutionStatus() === 'ActionTaken') {\r\n <label class=\"fly-moderation-queue__checkbox-row\">\r\n <input type=\"checkbox\" [checked]=\"deleteComment()\" (change)=\"onDeleteCommentToggle($event)\" />\r\n <span>{{ 'moderation.delete_comment_label' | translate }}</span>\r\n </label>\r\n }\r\n\r\n <textarea\r\n class=\"fly-moderation-queue__textarea\"\r\n rows=\"3\"\r\n [attr.aria-label]=\"'moderation.resolution_note_label' | translate\"\r\n [placeholder]=\"'moderation.resolution_note_label' | translate\"\r\n [ngModel]=\"resolutionNote()\"\r\n (ngModelChange)=\"onResolutionNoteChange($event)\"></textarea>\r\n\r\n <div class=\"fly-moderation-queue__dialog-actions\">\r\n <button type=\"button\" class=\"fly-moderation-queue__cancel-btn\" (click)=\"closeResolve()\">\r\n {{ 'common.action.cancel' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fly-moderation-queue__submit-btn\"\r\n [disabled]=\"!resolutionStatus()\"\r\n (click)=\"submitResolve(r)\">\r\n {{ 'moderation.resolve_submit' | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--surface-card, #fff);--_surface-hover: var(--surface-hover, #f1f5f9);--_border: var(--separator, var(--surface-border, #e2e8f0));--_text: var(--label-primary, var(--text-color, #0f172a));--_text-subtle: var(--label-secondary, var(--text-color-secondary, #64748b));--_text-tertiary: var(--label-tertiary, #94a3b8);--_fill: var(--fill-tertiary, #f3f4f6);--_accent: var(--accent);--_danger: var(--system-red, #ef4444);--_blue: var(--system-blue, #0a84ff);--_orange: var(--system-orange, #ff9f0a);--_green: var(--system-green, #32d74b);--_radius: 8px;--_blue-text: #1d4ed8;--_orange-text: #9a3412;--_green-text: #166534;--_danger-text: #b91c1c;display:block;inline-size:100%;color:var(--_text);font-size:13px}.fly-moderation-queue__body{display:flex;flex-direction:column;gap:16px}.fly-moderation-queue__header{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.fly-moderation-queue__count{font-weight:700;font-size:14px}.fly-moderation-queue__status-filter{display:inline-flex;flex-wrap:wrap;border:1px solid var(--_border);border-radius:var(--_radius);overflow:hidden}.fly-moderation-queue__status-btn{padding:5px 12px;border:none;border-inline-end:1px solid var(--_border);background:transparent;color:var(--_text-subtle);font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__status-btn:last-child{border-inline-end:none}.fly-moderation-queue__status-btn:disabled{opacity:.6;cursor:not-allowed}.fly-moderation-queue__status-btn--active{background:var(--_accent);color:#fff}.fly-moderation-queue__empty,.fly-moderation-queue__status-msg{margin:0;color:var(--_text-subtle);text-align:center;padding:16px 0}.fly-moderation-queue__error{margin:0;padding:8px 12px;border-radius:var(--_radius);background:color-mix(in srgb,var(--_danger) 12%,transparent);color:var(--_danger);font-size:12px}.fly-moderation-queue__sr-only{position:absolute;inline-size:1px;block-size:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.fly-moderation-queue__table-wrap{overflow-x:auto}.fly-moderation-queue__table{inline-size:100%;border-collapse:collapse;font-size:12px}.fly-moderation-queue__table th{text-align:start;padding:6px 10px;color:var(--_text-subtle);font-weight:600;border-block-end:1px solid var(--_border);white-space:nowrap}.fly-moderation-queue__table td{padding:8px 10px;border-block-end:1px solid var(--_border);vertical-align:top}.fly-moderation-queue__reason-cell{display:table-cell}.fly-moderation-queue__reason{display:block;font-weight:600}.fly-moderation-queue__detail{display:block;margin-block-start:2px;color:var(--_text-tertiary);font-size:11px;max-inline-size:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-moderation-queue__excerpt{max-inline-size:260px;color:var(--_text-subtle);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-moderation-queue__subject-link{border:none;background:transparent;padding:0;color:var(--_accent);font:inherit;font-weight:600;cursor:pointer;text-align:start}.fly-moderation-queue__subject-link:hover{text-decoration:underline}.fly-moderation-queue__badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:700;white-space:nowrap}.fly-moderation-queue__badge--Open{background:color-mix(in srgb,var(--_blue) 12%,#fff);color:var(--_blue-text)}.fly-moderation-queue__badge--UnderReview{background:color-mix(in srgb,var(--_orange) 12%,#fff);color:var(--_orange-text)}.fly-moderation-queue__badge--ActionTaken{background:color-mix(in srgb,var(--_danger) 12%,#fff);color:var(--_danger-text)}.fly-moderation-queue__badge--Dismissed{background:color-mix(in srgb,var(--_green) 12%,#fff);color:var(--_green-text)}.fly-moderation-queue__resolution-note{margin-block-start:4px;color:var(--_text-tertiary);font-size:11px;max-inline-size:200px}.fly-moderation-queue__actions{display:flex;flex-direction:column;align-items:flex-start;gap:6px;white-space:nowrap}.fly-moderation-queue__action-btn{border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);padding:4px 10px;color:var(--_text);font:inherit;font-size:11px;font-weight:600;cursor:pointer}.fly-moderation-queue__action-btn:hover:not(:disabled){background:var(--_surface-hover)}.fly-moderation-queue__action-btn:disabled{opacity:.6;cursor:not-allowed}.fly-moderation-queue__lock-confirm{display:inline-flex;align-items:center;gap:6px}.fly-moderation-queue__load-more{align-self:flex-start;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);padding:6px 14px;color:var(--_text);font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__load-more:hover:not(:disabled){background:var(--_surface-hover)}.fly-moderation-queue__load-more:disabled{opacity:.6;cursor:not-allowed}.fly-moderation-queue__overlay{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;z-index:1000}.fly-moderation-queue__overlay-backdrop{position:absolute;inset:0;background:#00000073;border:none;padding:0;margin:0;cursor:pointer}.fly-moderation-queue__dialog{position:relative;z-index:1;inline-size:min(440px,90vw);max-block-size:85vh;overflow-y:auto;background:var(--_surface);border-radius:12px;padding:20px;display:flex;flex-direction:column;gap:14px;box-shadow:0 12px 32px #00000040}.fly-moderation-queue__dialog:focus-visible{outline:2px solid var(--_accent);outline-offset:2px}.fly-moderation-queue__dialog-title{margin:0;font-size:16px;font-weight:700}.fly-moderation-queue__resolution-group{display:flex;flex-direction:column;gap:8px;margin:0;padding:0;border:none}.fly-moderation-queue__resolution-group legend{font-weight:600;padding:0;margin-block-end:4px}.fly-moderation-queue__resolution-option,.fly-moderation-queue__checkbox-row{display:flex;align-items:center;gap:8px;cursor:pointer}.fly-moderation-queue__resolution-option input,.fly-moderation-queue__checkbox-row input{accent-color:var(--_accent);cursor:pointer}.fly-moderation-queue__textarea{inline-size:100%;padding:8px 12px;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);color:var(--_text);font:inherit;line-height:1.5;resize:vertical}.fly-moderation-queue__textarea:focus-visible{outline:2px solid var(--_accent);outline-offset:1px}.fly-moderation-queue__dialog-actions{display:flex;justify-content:flex-end;gap:8px}.fly-moderation-queue__submit-btn{padding:6px 16px;border:none;border-radius:var(--_radius);background:var(--_accent);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__submit-btn:hover:not(:disabled){opacity:.9}.fly-moderation-queue__submit-btn:disabled{opacity:.5;cursor:not-allowed}.fly-moderation-queue__cancel-btn{padding:6px 14px;border:1px solid var(--_border);border-radius:var(--_radius);background:transparent;color:var(--_text-subtle);font:inherit;cursor:pointer}.fly-moderation-queue__cancel-btn:hover{background:var(--_surface-hover)}.fly-moderation-queue__danger-btn{padding:6px 14px;border:none;border-radius:var(--_radius);background:var(--_danger);color:#fff;font:inherit;font-weight:600;cursor:pointer}.fly-moderation-queue__danger-btn:hover{opacity:.9}\n"] }]
16574
+ }], propDecorators: { page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: true }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: false }] }], resolveDisplayName: [{ type: i0.Input, args: [{ isSignal: true, alias: "resolveDisplayName", required: false }] }], loadPage: [{ type: i0.Output, args: ["loadPage"] }], resolve: [{ type: i0.Output, args: ["resolve"] }], lockThread: [{ type: i0.Output, args: ["lockThread"] }], openSubject: [{ type: i0.Output, args: ["openSubject"] }], onEscape: [{
16099
16575
  type: HostListener,
16100
16576
  args: ['document:keydown.escape']
16101
16577
  }] } });
@@ -16245,11 +16721,11 @@ class FlyPeoplePickerComponent {
16245
16721
  this.selectionChange.emit(this._selected().map((o) => o.id));
16246
16722
  }
16247
16723
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyPeoplePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
16248
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyPeoplePickerComponent, isStandalone: true, selector: "fly-people-picker", inputs: { searchFn: { classPropertyName: "searchFn", publicName: "searchFn", isSignal: true, isRequired: true, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, excludeIds: { classPropertyName: "excludeIds", publicName: "excludeIds", isSignal: true, isRequired: false, transformFunction: null }, initialSelection: { classPropertyName: "initialSelection", publicName: "initialSelection", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange" }, host: { classAttribute: "fly-people-picker" }, viewQueries: [{ propertyName: "typeahead", first: true, predicate: FlyTypeaheadComponent, descendants: true }], ngImport: i0, template: "@if (selected().length > 0) {\n <ul class=\"fly-people-picker__chips\" role=\"list\">\n @for (o of selected(); track o.id) {\n <li class=\"fly-people-picker__chip\" role=\"listitem\">\n <span class=\"fly-people-picker__chip-avatar\" aria-hidden=\"true\">\n @if (o.avatarUrl) {\n <img [src]=\"o.avatarUrl\" alt=\"\" />\n } @else {\n <span class=\"fly-people-picker__chip-initials\">{{ initialsFor(o.displayName) }}</span>\n }\n </span>\n <span class=\"fly-people-picker__chip-name\" [title]=\"o.email || o.displayName\">{{ o.displayName }}</span>\n <button\n type=\"button\"\n class=\"fly-people-picker__chip-remove\"\n [attr.aria-label]=\"'people_picker.remove' | translate: { name: o.displayName }\"\n (click)=\"remove(o.id)\">\n \u2715\n </button>\n </li>\n }\n </ul>\n}\n\n@if (showSearch()) {\n <fly-typeahead\n [searchFn]=\"typeaheadSearchFn\"\n [placeholder]=\"placeholder() || ('people_picker.search_placeholder' | translate)\"\n [allowFreeText]=\"false\"\n (selected)=\"onPicked($event)\" />\n} @else {\n <button type=\"button\" class=\"fly-people-picker__change-btn\" (click)=\"change()\">\n {{ 'people_picker.change' | translate }}\n </button>\n}\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--surface-card, #fff);--_surface-hover: var(--surface-hover, #f1f5f9);--_border: var(--separator, var(--surface-border, #e2e8f0));--_text: var(--label-primary, var(--text-color, #0f172a));--_text-subtle: var(--label-secondary, var(--text-color-secondary, #64748b));--_fill: var(--fill-tertiary, #f3f4f6);--_accent: var(--accent);--_danger: var(--system-red, #ef4444);--_radius: 999px;display:flex;flex-direction:column;gap:8px;inline-size:100%;color:var(--_text);font-size:13px}.fly-people-picker__chips{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:6px}.fly-people-picker__chip{display:inline-flex;align-items:center;gap:6px;padding-block:3px;padding-inline:3px 6px;border-radius:var(--_radius);background:var(--_fill);max-inline-size:220px}.fly-people-picker__chip-avatar{inline-size:22px;block-size:22px;border-radius:50%;overflow:hidden;flex:none;background:var(--_surface);display:flex;align-items:center;justify-content:center}.fly-people-picker__chip-avatar img{inline-size:100%;block-size:100%;object-fit:cover}.fly-people-picker__chip-initials{font-size:9px;font-weight:700;color:var(--_text-subtle);text-transform:uppercase}.fly-people-picker__chip-name{font-size:12px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-people-picker__chip-remove{border:none;background:transparent;padding:2px;margin-inline-start:2px;border-radius:50%;color:var(--_text-subtle);font-size:10px;line-height:1;cursor:pointer;flex:none}.fly-people-picker__chip-remove:hover{background:color-mix(in srgb,var(--_danger) 18%,transparent);color:var(--_danger)}.fly-people-picker__change-btn{align-self:flex-start;border:1px solid var(--_border);border-radius:8px;background:var(--_surface);padding:5px 12px;color:var(--_text);font:inherit;font-weight:600;cursor:pointer}.fly-people-picker__change-btn:hover{background:var(--_surface-hover)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: FlyTypeaheadComponent, selector: "fly-typeahead", inputs: ["searchFn", "debounceMs", "placeholder", "ariaLabel", "value", "allowFreeText", "openOnFocus"], outputs: ["valueChange", "selected", "cleared"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
16724
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyPeoplePickerComponent, isStandalone: true, selector: "fly-people-picker", inputs: { searchFn: { classPropertyName: "searchFn", publicName: "searchFn", isSignal: true, isRequired: true, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, excludeIds: { classPropertyName: "excludeIds", publicName: "excludeIds", isSignal: true, isRequired: false, transformFunction: null }, initialSelection: { classPropertyName: "initialSelection", publicName: "initialSelection", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange" }, host: { classAttribute: "fly-people-picker" }, viewQueries: [{ propertyName: "typeahead", first: true, predicate: FlyTypeaheadComponent, descendants: true }], ngImport: i0, template: "@if (selected().length > 0) {\r\n <ul class=\"fly-people-picker__chips\" role=\"list\">\r\n @for (o of selected(); track o.id) {\r\n <li class=\"fly-people-picker__chip\" role=\"listitem\">\r\n <span class=\"fly-people-picker__chip-avatar\" aria-hidden=\"true\">\r\n @if (o.avatarUrl) {\r\n <img [src]=\"o.avatarUrl\" alt=\"\" />\r\n } @else {\r\n <span class=\"fly-people-picker__chip-initials\">{{ initialsFor(o.displayName) }}</span>\r\n }\r\n </span>\r\n <span class=\"fly-people-picker__chip-name\" [title]=\"o.email || o.displayName\">{{ o.displayName }}</span>\r\n <button\r\n type=\"button\"\r\n class=\"fly-people-picker__chip-remove\"\r\n [attr.aria-label]=\"'people_picker.remove' | translate: { name: o.displayName }\"\r\n (click)=\"remove(o.id)\">\r\n \u2715\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n}\r\n\r\n@if (showSearch()) {\r\n <fly-typeahead\r\n [searchFn]=\"typeaheadSearchFn\"\r\n [placeholder]=\"placeholder() || ('people_picker.search_placeholder' | translate)\"\r\n [allowFreeText]=\"false\"\r\n (selected)=\"onPicked($event)\" />\r\n} @else {\r\n <button type=\"button\" class=\"fly-people-picker__change-btn\" (click)=\"change()\">\r\n {{ 'people_picker.change' | translate }}\r\n </button>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--surface-card, #fff);--_surface-hover: var(--surface-hover, #f1f5f9);--_border: var(--separator, var(--surface-border, #e2e8f0));--_text: var(--label-primary, var(--text-color, #0f172a));--_text-subtle: var(--label-secondary, var(--text-color-secondary, #64748b));--_fill: var(--fill-tertiary, #f3f4f6);--_accent: var(--accent);--_danger: var(--system-red, #ef4444);--_radius: 999px;display:flex;flex-direction:column;gap:8px;inline-size:100%;color:var(--_text);font-size:13px}.fly-people-picker__chips{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:6px}.fly-people-picker__chip{display:inline-flex;align-items:center;gap:6px;padding-block:3px;padding-inline:3px 6px;border-radius:var(--_radius);background:var(--_fill);max-inline-size:220px}.fly-people-picker__chip-avatar{inline-size:22px;block-size:22px;border-radius:50%;overflow:hidden;flex:none;background:var(--_surface);display:flex;align-items:center;justify-content:center}.fly-people-picker__chip-avatar img{inline-size:100%;block-size:100%;object-fit:cover}.fly-people-picker__chip-initials{font-size:9px;font-weight:700;color:var(--_text-subtle);text-transform:uppercase}.fly-people-picker__chip-name{font-size:12px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-people-picker__chip-remove{border:none;background:transparent;padding:2px;margin-inline-start:2px;border-radius:50%;color:var(--_text-subtle);font-size:10px;line-height:1;cursor:pointer;flex:none}.fly-people-picker__chip-remove:hover{background:color-mix(in srgb,var(--_danger) 18%,transparent);color:var(--_danger)}.fly-people-picker__change-btn{align-self:flex-start;border:1px solid var(--_border);border-radius:8px;background:var(--_surface);padding:5px 12px;color:var(--_text);font:inherit;font-weight:600;cursor:pointer}.fly-people-picker__change-btn:hover{background:var(--_surface-hover)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: FlyTypeaheadComponent, selector: "fly-typeahead", inputs: ["searchFn", "debounceMs", "placeholder", "ariaLabel", "value", "allowFreeText", "openOnFocus"], outputs: ["valueChange", "selected", "cleared"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
16249
16725
  }
16250
16726
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyPeoplePickerComponent, decorators: [{
16251
16727
  type: Component,
16252
- args: [{ selector: 'fly-people-picker', standalone: true, imports: [CommonModule, TranslatePipe, FlyTypeaheadComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'fly-people-picker' }, template: "@if (selected().length > 0) {\n <ul class=\"fly-people-picker__chips\" role=\"list\">\n @for (o of selected(); track o.id) {\n <li class=\"fly-people-picker__chip\" role=\"listitem\">\n <span class=\"fly-people-picker__chip-avatar\" aria-hidden=\"true\">\n @if (o.avatarUrl) {\n <img [src]=\"o.avatarUrl\" alt=\"\" />\n } @else {\n <span class=\"fly-people-picker__chip-initials\">{{ initialsFor(o.displayName) }}</span>\n }\n </span>\n <span class=\"fly-people-picker__chip-name\" [title]=\"o.email || o.displayName\">{{ o.displayName }}</span>\n <button\n type=\"button\"\n class=\"fly-people-picker__chip-remove\"\n [attr.aria-label]=\"'people_picker.remove' | translate: { name: o.displayName }\"\n (click)=\"remove(o.id)\">\n \u2715\n </button>\n </li>\n }\n </ul>\n}\n\n@if (showSearch()) {\n <fly-typeahead\n [searchFn]=\"typeaheadSearchFn\"\n [placeholder]=\"placeholder() || ('people_picker.search_placeholder' | translate)\"\n [allowFreeText]=\"false\"\n (selected)=\"onPicked($event)\" />\n} @else {\n <button type=\"button\" class=\"fly-people-picker__change-btn\" (click)=\"change()\">\n {{ 'people_picker.change' | translate }}\n </button>\n}\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--surface-card, #fff);--_surface-hover: var(--surface-hover, #f1f5f9);--_border: var(--separator, var(--surface-border, #e2e8f0));--_text: var(--label-primary, var(--text-color, #0f172a));--_text-subtle: var(--label-secondary, var(--text-color-secondary, #64748b));--_fill: var(--fill-tertiary, #f3f4f6);--_accent: var(--accent);--_danger: var(--system-red, #ef4444);--_radius: 999px;display:flex;flex-direction:column;gap:8px;inline-size:100%;color:var(--_text);font-size:13px}.fly-people-picker__chips{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:6px}.fly-people-picker__chip{display:inline-flex;align-items:center;gap:6px;padding-block:3px;padding-inline:3px 6px;border-radius:var(--_radius);background:var(--_fill);max-inline-size:220px}.fly-people-picker__chip-avatar{inline-size:22px;block-size:22px;border-radius:50%;overflow:hidden;flex:none;background:var(--_surface);display:flex;align-items:center;justify-content:center}.fly-people-picker__chip-avatar img{inline-size:100%;block-size:100%;object-fit:cover}.fly-people-picker__chip-initials{font-size:9px;font-weight:700;color:var(--_text-subtle);text-transform:uppercase}.fly-people-picker__chip-name{font-size:12px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-people-picker__chip-remove{border:none;background:transparent;padding:2px;margin-inline-start:2px;border-radius:50%;color:var(--_text-subtle);font-size:10px;line-height:1;cursor:pointer;flex:none}.fly-people-picker__chip-remove:hover{background:color-mix(in srgb,var(--_danger) 18%,transparent);color:var(--_danger)}.fly-people-picker__change-btn{align-self:flex-start;border:1px solid var(--_border);border-radius:8px;background:var(--_surface);padding:5px 12px;color:var(--_text);font:inherit;font-weight:600;cursor:pointer}.fly-people-picker__change-btn:hover{background:var(--_surface-hover)}\n"] }]
16728
+ args: [{ selector: 'fly-people-picker', standalone: true, imports: [CommonModule, TranslatePipe, FlyTypeaheadComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'fly-people-picker' }, template: "@if (selected().length > 0) {\r\n <ul class=\"fly-people-picker__chips\" role=\"list\">\r\n @for (o of selected(); track o.id) {\r\n <li class=\"fly-people-picker__chip\" role=\"listitem\">\r\n <span class=\"fly-people-picker__chip-avatar\" aria-hidden=\"true\">\r\n @if (o.avatarUrl) {\r\n <img [src]=\"o.avatarUrl\" alt=\"\" />\r\n } @else {\r\n <span class=\"fly-people-picker__chip-initials\">{{ initialsFor(o.displayName) }}</span>\r\n }\r\n </span>\r\n <span class=\"fly-people-picker__chip-name\" [title]=\"o.email || o.displayName\">{{ o.displayName }}</span>\r\n <button\r\n type=\"button\"\r\n class=\"fly-people-picker__chip-remove\"\r\n [attr.aria-label]=\"'people_picker.remove' | translate: { name: o.displayName }\"\r\n (click)=\"remove(o.id)\">\r\n \u2715\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n}\r\n\r\n@if (showSearch()) {\r\n <fly-typeahead\r\n [searchFn]=\"typeaheadSearchFn\"\r\n [placeholder]=\"placeholder() || ('people_picker.search_placeholder' | translate)\"\r\n [allowFreeText]=\"false\"\r\n (selected)=\"onPicked($event)\" />\r\n} @else {\r\n <button type=\"button\" class=\"fly-people-picker__change-btn\" (click)=\"change()\">\r\n {{ 'people_picker.change' | translate }}\r\n </button>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--surface-card, #fff);--_surface-hover: var(--surface-hover, #f1f5f9);--_border: var(--separator, var(--surface-border, #e2e8f0));--_text: var(--label-primary, var(--text-color, #0f172a));--_text-subtle: var(--label-secondary, var(--text-color-secondary, #64748b));--_fill: var(--fill-tertiary, #f3f4f6);--_accent: var(--accent);--_danger: var(--system-red, #ef4444);--_radius: 999px;display:flex;flex-direction:column;gap:8px;inline-size:100%;color:var(--_text);font-size:13px}.fly-people-picker__chips{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:6px}.fly-people-picker__chip{display:inline-flex;align-items:center;gap:6px;padding-block:3px;padding-inline:3px 6px;border-radius:var(--_radius);background:var(--_fill);max-inline-size:220px}.fly-people-picker__chip-avatar{inline-size:22px;block-size:22px;border-radius:50%;overflow:hidden;flex:none;background:var(--_surface);display:flex;align-items:center;justify-content:center}.fly-people-picker__chip-avatar img{inline-size:100%;block-size:100%;object-fit:cover}.fly-people-picker__chip-initials{font-size:9px;font-weight:700;color:var(--_text-subtle);text-transform:uppercase}.fly-people-picker__chip-name{font-size:12px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-people-picker__chip-remove{border:none;background:transparent;padding:2px;margin-inline-start:2px;border-radius:50%;color:var(--_text-subtle);font-size:10px;line-height:1;cursor:pointer;flex:none}.fly-people-picker__chip-remove:hover{background:color-mix(in srgb,var(--_danger) 18%,transparent);color:var(--_danger)}.fly-people-picker__change-btn{align-self:flex-start;border:1px solid var(--_border);border-radius:8px;background:var(--_surface);padding:5px 12px;color:var(--_text);font:inherit;font-weight:600;cursor:pointer}.fly-people-picker__change-btn:hover{background:var(--_surface-hover)}\n"] }]
16253
16729
  }], ctorParameters: () => [], propDecorators: { searchFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchFn", required: true }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], excludeIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "excludeIds", required: false }] }], initialSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialSelection", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], typeahead: [{
16254
16730
  type: ViewChild,
16255
16731
  args: [FlyTypeaheadComponent]
@@ -17322,8 +17798,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
17322
17798
  * Circular 34px icon-only button (ported from the legacy global
17323
17799
  * `.circles-iconbtn` disc — both render identically until the P6 sweep).
17324
17800
  *
17325
- * `tooltipKey` resolves through {@link I18nService} and wires BOTH
17326
- * `data-tooltip` (CSS tooltip) and `aria-label`, re-resolving on locale change.
17801
+ * `tooltipKey` resolves through {@link I18nService} and wires BOTH `data-tooltip` and
17802
+ * `aria-label`, re-resolving on locale change.
17803
+ *
17804
+ * The label is FLOATED by {@link FlyTooltipDirective} (a body-parented `position: fixed`
17805
+ * node), listed here as a host directive. It used to be a CSS `::before` on
17806
+ * `[data-tooltip]`, which any scrolling or `overflow: hidden` ancestor clipped — the
17807
+ * boards toolbar rail cut every tip off inside its own column. The directive reads the
17808
+ * attribute this component writes, so there is no input to keep in sync.
17327
17809
  */
17328
17810
  class FlyIconButtonComponent {
17329
17811
  i18n = inject(I18nService);
@@ -17356,11 +17838,11 @@ class FlyIconButtonComponent {
17356
17838
  });
17357
17839
  }
17358
17840
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyIconButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
17359
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.19", type: FlyIconButtonComponent, isStandalone: true, selector: "button[fly-icon-button]", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, tooltipKey: { classPropertyName: "tooltipKey", publicName: "tooltipKey", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-variant": "variant()", "class.on": "active()" } }, ngImport: i0, template: '<ng-content />', isInline: true, styles: [":host{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;flex-shrink:0;padding:0;border-radius:50%;background:var(--bg-2);border:1px solid var(--line);color:var(--ink-2);cursor:pointer;font-family:inherit;transition:background var(--t-state),border-color var(--t-state),color var(--t-state)}:host:hover{background:var(--bg-3);color:var(--ink);border-color:var(--line-2)}:host:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}:host:disabled{opacity:.5;cursor:not-allowed}:host ::ng-deep svg{width:16px;height:16px}:host .pi,:host ::ng-deep .pi{font-size:16px}:host(.on){background:var(--bg-3);border-color:var(--line-2);color:var(--ink)}:host([data-variant=primary]){background:var(--grad);border:0;color:var(--ink-inverse);box-shadow:var(--shadow-raised)}:host([data-variant=primary]):hover{background:var(--grad);border:0;color:var(--ink-inverse);filter:brightness(1.05);box-shadow:var(--shadow-accent)}:host([data-variant=danger]){color:var(--danger)}:host([data-variant=danger]):hover{background:var(--danger-bg);border-color:var(--danger-line);color:var(--danger)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
17841
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.19", type: FlyIconButtonComponent, isStandalone: true, selector: "button[fly-icon-button]", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, tooltipKey: { classPropertyName: "tooltipKey", publicName: "tooltipKey", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-variant": "variant()", "class.on": "active()" } }, hostDirectives: [{ directive: FlyTooltipDirective }], ngImport: i0, template: '<ng-content />', isInline: true, styles: [":host{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;flex-shrink:0;padding:0;border-radius:50%;background:var(--bg-2);border:1px solid var(--line);color:var(--ink-2);cursor:pointer;font-family:inherit;transition:background var(--t-state),border-color var(--t-state),color var(--t-state)}:host:hover{background:var(--bg-3);color:var(--ink);border-color:var(--line-2)}:host:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}:host:disabled{opacity:.5;cursor:not-allowed}:host ::ng-deep svg{width:16px;height:16px}:host .pi,:host ::ng-deep .pi{font-size:16px}:host(.on){background:var(--bg-3);border-color:var(--line-2);color:var(--ink)}:host([data-variant=primary]){background:var(--grad);border:0;color:var(--ink-inverse);box-shadow:var(--shadow-raised)}:host([data-variant=primary]):hover{background:var(--grad);border:0;color:var(--ink-inverse);filter:brightness(1.05);box-shadow:var(--shadow-accent)}:host([data-variant=danger]){color:var(--danger)}:host([data-variant=danger]):hover{background:var(--danger-bg);border-color:var(--danger-line);color:var(--danger)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
17360
17842
  }
17361
17843
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyIconButtonComponent, decorators: [{
17362
17844
  type: Component,
17363
- args: [{ selector: 'button[fly-icon-button]', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, host: {
17845
+ args: [{ selector: 'button[fly-icon-button]', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, hostDirectives: [FlyTooltipDirective], host: {
17364
17846
  '[attr.data-variant]': 'variant()',
17365
17847
  '[class.on]': 'active()',
17366
17848
  }, template: '<ng-content />', styles: [":host{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;flex-shrink:0;padding:0;border-radius:50%;background:var(--bg-2);border:1px solid var(--line);color:var(--ink-2);cursor:pointer;font-family:inherit;transition:background var(--t-state),border-color var(--t-state),color var(--t-state)}:host:hover{background:var(--bg-3);color:var(--ink);border-color:var(--line-2)}:host:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}:host:disabled{opacity:.5;cursor:not-allowed}:host ::ng-deep svg{width:16px;height:16px}:host .pi,:host ::ng-deep .pi{font-size:16px}:host(.on){background:var(--bg-3);border-color:var(--line-2);color:var(--ink)}:host([data-variant=primary]){background:var(--grad);border:0;color:var(--ink-inverse);box-shadow:var(--shadow-raised)}:host([data-variant=primary]):hover{background:var(--grad);border:0;color:var(--ink-inverse);filter:brightness(1.05);box-shadow:var(--shadow-accent)}:host([data-variant=danger]){color:var(--danger)}:host([data-variant=danger]):hover{background:var(--danger-bg);border-color:var(--danger-line);color:var(--danger)}\n"] }]
@@ -17519,11 +18001,11 @@ class StatusBadgeComponent {
17519
18001
  return `common.status.${this.status().toLowerCase()}`;
17520
18002
  }
17521
18003
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: StatusBadgeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
17522
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: StatusBadgeComponent, isStandalone: true, selector: "fly-status-badge", inputs: { status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: true, transformFunction: null }, dot: { classPropertyName: "dot", publicName: "dot", isSignal: true, isRequired: false, transformFunction: null }, domain: { classPropertyName: "domain", publicName: "domain", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (dot()) {\r\n <!-- \"Status light\": a colour-coded dot with a soft concentric halo in the\r\n status colour. The label is shown on hover (global [data-tooltip]) and\r\n read by screen readers (aria-label) \u2014 compact, glanceable, and modern. -->\r\n <span\r\n class=\"status-dot\"\r\n [ngClass]=\"'status-dot--' + statusClass()\"\r\n [attr.data-tooltip]=\"labelKey() | translate\"\r\n [attr.aria-label]=\"labelKey() | translate\"\r\n role=\"img\"\r\n tabindex=\"0\"\r\n ></span>\r\n} @else {\r\n <span class=\"status-badge\" [ngClass]=\"'status-badge--' + statusClass()\">\r\n {{ labelKey() | translate }}\r\n </span>\r\n}\r\n", styles: ["@charset \"UTF-8\";.status-badge{display:inline-flex;align-items:center;padding:2px 10px;border-radius:999px;font-size:12px;font-weight:var(--fw-medium)}.status-dot{display:inline-block;inline-size:10px;block-size:10px;border-radius:50%;background:currentColor;box-shadow:0 0 0 3px color-mix(in srgb,currentColor 16%,transparent);cursor:default;transition:transform var(--t-state),box-shadow var(--t-state)}.status-dot:hover,.status-dot:focus-visible{transform:scale(1.18);box-shadow:0 0 0 4px color-mix(in srgb,currentColor 26%,transparent);outline:none}.status-dot--draft{color:var(--color-aeblack-500, var(--ink-3))}.status-dot--review{color:var(--color-aeorange-700, var(--warning-fg))}.status-dot--approved{color:var(--color-aegreen-800, var(--success))}.status-dot--published{color:var(--color-techblue-800, var(--st-approved))}.status-dot--archived{color:var(--color-aeblack-500, var(--ink-3))}.status-dot--planned{color:var(--color-techblue-800, var(--st-approved))}.status-dot--inprogress{color:var(--color-aeorange-700, var(--warning-fg))}.status-dot--completed{color:var(--color-aegreen-800, var(--success))}.status-dot--onhold{color:var(--color-aeorange-800, var(--warning-fg))}.status-dot--cancelled{color:var(--color-aered-800, var(--danger))}.status-badge--draft{background:var(--color-aeblack-100, var(--st-draft-bg));color:var(--color-aeblack-700, var(--st-draft))}.status-badge--review{background:var(--color-aeorange-100, var(--warning-bg));color:var(--color-aeorange-700, var(--warning-fg))}.status-badge--approved{background:var(--color-aegreen-100, var(--success-bg));color:var(--color-aegreen-800, var(--success))}.status-badge--published{background:var(--color-techblue-100, var(--st-approved-bg));color:var(--color-techblue-800, var(--st-approved))}.status-badge--archived{background:var(--color-aeblack-100, var(--st-draft-bg));color:var(--color-aeblack-500, var(--ink-3))}.status-badge--planned{background:var(--color-techblue-100, var(--st-approved-bg));color:var(--color-techblue-800, var(--st-approved))}.status-badge--inprogress{background:var(--color-aeorange-100, var(--warning-bg));color:var(--color-aeorange-700, var(--warning-fg))}.status-badge--completed{background:var(--color-aegreen-100, var(--success-bg));color:var(--color-aegreen-800, var(--success))}.status-badge--onhold{background:var(--color-aeorange-200, var(--warning-bg));color:var(--color-aeorange-800, var(--warning-fg))}.status-badge--cancelled{background:var(--color-aered-100, var(--danger-bg));color:var(--color-aered-800, var(--danger))}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
18004
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: StatusBadgeComponent, isStandalone: true, selector: "fly-status-badge", inputs: { status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: true, transformFunction: null }, dot: { classPropertyName: "dot", publicName: "dot", isSignal: true, isRequired: false, transformFunction: null }, domain: { classPropertyName: "domain", publicName: "domain", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (dot()) {\r\n <!-- \"Status light\": a colour-coded dot with a soft concentric halo in the\r\n status colour. The label is shown on hover (`flyTooltip`) and read by screen readers\r\n (aria-label) \u2014 compact, glanceable, and modern.\r\n `[flyTooltip]` and NOT `[attr.data-tooltip]`: Angular does not selector-match a directive\r\n against an `attr.`-prefixed binding, so the attribute form compiles to a bare attribute with\r\n nothing listening \u2014 which, now that the CSS pseudo-element tooltip is gone, would show no\r\n label at all. `data-tooltip` stays alongside it as the styling/test hook it always was. -->\r\n <span\r\n class=\"status-dot\"\r\n [ngClass]=\"'status-dot--' + statusClass()\"\r\n [flyTooltip]=\"labelKey() | translate\"\r\n [attr.data-tooltip]=\"labelKey() | translate\"\r\n [attr.aria-label]=\"labelKey() | translate\"\r\n role=\"img\"\r\n tabindex=\"0\"\r\n ></span>\r\n} @else {\r\n <span class=\"status-badge\" [ngClass]=\"'status-badge--' + statusClass()\">\r\n {{ labelKey() | translate }}\r\n </span>\r\n}\r\n", styles: ["@charset \"UTF-8\";.status-badge{display:inline-flex;align-items:center;padding:2px 10px;border-radius:999px;font-size:12px;font-weight:var(--fw-medium)}.status-dot{display:inline-block;inline-size:10px;block-size:10px;border-radius:50%;background:currentColor;box-shadow:0 0 0 3px color-mix(in srgb,currentColor 16%,transparent);cursor:default;transition:transform var(--t-state),box-shadow var(--t-state)}.status-dot:hover,.status-dot:focus-visible{transform:scale(1.18);box-shadow:0 0 0 4px color-mix(in srgb,currentColor 26%,transparent);outline:none}.status-dot--draft{color:var(--color-aeblack-500, var(--ink-3))}.status-dot--review{color:var(--color-aeorange-700, var(--warning-fg))}.status-dot--approved{color:var(--color-aegreen-800, var(--success))}.status-dot--published{color:var(--color-techblue-800, var(--st-approved))}.status-dot--archived{color:var(--color-aeblack-500, var(--ink-3))}.status-dot--planned{color:var(--color-techblue-800, var(--st-approved))}.status-dot--inprogress{color:var(--color-aeorange-700, var(--warning-fg))}.status-dot--completed{color:var(--color-aegreen-800, var(--success))}.status-dot--onhold{color:var(--color-aeorange-800, var(--warning-fg))}.status-dot--cancelled{color:var(--color-aered-800, var(--danger))}.status-badge--draft{background:var(--color-aeblack-100, var(--st-draft-bg));color:var(--color-aeblack-700, var(--st-draft))}.status-badge--review{background:var(--color-aeorange-100, var(--warning-bg));color:var(--color-aeorange-700, var(--warning-fg))}.status-badge--approved{background:var(--color-aegreen-100, var(--success-bg));color:var(--color-aegreen-800, var(--success))}.status-badge--published{background:var(--color-techblue-100, var(--st-approved-bg));color:var(--color-techblue-800, var(--st-approved))}.status-badge--archived{background:var(--color-aeblack-100, var(--st-draft-bg));color:var(--color-aeblack-500, var(--ink-3))}.status-badge--planned{background:var(--color-techblue-100, var(--st-approved-bg));color:var(--color-techblue-800, var(--st-approved))}.status-badge--inprogress{background:var(--color-aeorange-100, var(--warning-bg));color:var(--color-aeorange-700, var(--warning-fg))}.status-badge--completed{background:var(--color-aegreen-100, var(--success-bg));color:var(--color-aegreen-800, var(--success))}.status-badge--onhold{background:var(--color-aeorange-200, var(--warning-bg));color:var(--color-aeorange-800, var(--warning-fg))}.status-badge--cancelled{background:var(--color-aered-100, var(--danger-bg));color:var(--color-aered-800, var(--danger))}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: FlyTooltipDirective, selector: "[flyTooltip], [data-tooltip]", inputs: ["flyTooltip", "flyTooltipPlacement", "flyTooltipDisabled", "flyTooltipDelay"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
17523
18005
  }
17524
18006
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: StatusBadgeComponent, decorators: [{
17525
18007
  type: Component,
17526
- args: [{ selector: 'fly-status-badge', standalone: true, imports: [NgClass, TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (dot()) {\r\n <!-- \"Status light\": a colour-coded dot with a soft concentric halo in the\r\n status colour. The label is shown on hover (global [data-tooltip]) and\r\n read by screen readers (aria-label) \u2014 compact, glanceable, and modern. -->\r\n <span\r\n class=\"status-dot\"\r\n [ngClass]=\"'status-dot--' + statusClass()\"\r\n [attr.data-tooltip]=\"labelKey() | translate\"\r\n [attr.aria-label]=\"labelKey() | translate\"\r\n role=\"img\"\r\n tabindex=\"0\"\r\n ></span>\r\n} @else {\r\n <span class=\"status-badge\" [ngClass]=\"'status-badge--' + statusClass()\">\r\n {{ labelKey() | translate }}\r\n </span>\r\n}\r\n", styles: ["@charset \"UTF-8\";.status-badge{display:inline-flex;align-items:center;padding:2px 10px;border-radius:999px;font-size:12px;font-weight:var(--fw-medium)}.status-dot{display:inline-block;inline-size:10px;block-size:10px;border-radius:50%;background:currentColor;box-shadow:0 0 0 3px color-mix(in srgb,currentColor 16%,transparent);cursor:default;transition:transform var(--t-state),box-shadow var(--t-state)}.status-dot:hover,.status-dot:focus-visible{transform:scale(1.18);box-shadow:0 0 0 4px color-mix(in srgb,currentColor 26%,transparent);outline:none}.status-dot--draft{color:var(--color-aeblack-500, var(--ink-3))}.status-dot--review{color:var(--color-aeorange-700, var(--warning-fg))}.status-dot--approved{color:var(--color-aegreen-800, var(--success))}.status-dot--published{color:var(--color-techblue-800, var(--st-approved))}.status-dot--archived{color:var(--color-aeblack-500, var(--ink-3))}.status-dot--planned{color:var(--color-techblue-800, var(--st-approved))}.status-dot--inprogress{color:var(--color-aeorange-700, var(--warning-fg))}.status-dot--completed{color:var(--color-aegreen-800, var(--success))}.status-dot--onhold{color:var(--color-aeorange-800, var(--warning-fg))}.status-dot--cancelled{color:var(--color-aered-800, var(--danger))}.status-badge--draft{background:var(--color-aeblack-100, var(--st-draft-bg));color:var(--color-aeblack-700, var(--st-draft))}.status-badge--review{background:var(--color-aeorange-100, var(--warning-bg));color:var(--color-aeorange-700, var(--warning-fg))}.status-badge--approved{background:var(--color-aegreen-100, var(--success-bg));color:var(--color-aegreen-800, var(--success))}.status-badge--published{background:var(--color-techblue-100, var(--st-approved-bg));color:var(--color-techblue-800, var(--st-approved))}.status-badge--archived{background:var(--color-aeblack-100, var(--st-draft-bg));color:var(--color-aeblack-500, var(--ink-3))}.status-badge--planned{background:var(--color-techblue-100, var(--st-approved-bg));color:var(--color-techblue-800, var(--st-approved))}.status-badge--inprogress{background:var(--color-aeorange-100, var(--warning-bg));color:var(--color-aeorange-700, var(--warning-fg))}.status-badge--completed{background:var(--color-aegreen-100, var(--success-bg));color:var(--color-aegreen-800, var(--success))}.status-badge--onhold{background:var(--color-aeorange-200, var(--warning-bg));color:var(--color-aeorange-800, var(--warning-fg))}.status-badge--cancelled{background:var(--color-aered-100, var(--danger-bg));color:var(--color-aered-800, var(--danger))}\n"] }]
18008
+ args: [{ selector: 'fly-status-badge', standalone: true, imports: [NgClass, TranslatePipe, FlyTooltipDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (dot()) {\r\n <!-- \"Status light\": a colour-coded dot with a soft concentric halo in the\r\n status colour. The label is shown on hover (`flyTooltip`) and read by screen readers\r\n (aria-label) \u2014 compact, glanceable, and modern.\r\n `[flyTooltip]` and NOT `[attr.data-tooltip]`: Angular does not selector-match a directive\r\n against an `attr.`-prefixed binding, so the attribute form compiles to a bare attribute with\r\n nothing listening \u2014 which, now that the CSS pseudo-element tooltip is gone, would show no\r\n label at all. `data-tooltip` stays alongside it as the styling/test hook it always was. -->\r\n <span\r\n class=\"status-dot\"\r\n [ngClass]=\"'status-dot--' + statusClass()\"\r\n [flyTooltip]=\"labelKey() | translate\"\r\n [attr.data-tooltip]=\"labelKey() | translate\"\r\n [attr.aria-label]=\"labelKey() | translate\"\r\n role=\"img\"\r\n tabindex=\"0\"\r\n ></span>\r\n} @else {\r\n <span class=\"status-badge\" [ngClass]=\"'status-badge--' + statusClass()\">\r\n {{ labelKey() | translate }}\r\n </span>\r\n}\r\n", styles: ["@charset \"UTF-8\";.status-badge{display:inline-flex;align-items:center;padding:2px 10px;border-radius:999px;font-size:12px;font-weight:var(--fw-medium)}.status-dot{display:inline-block;inline-size:10px;block-size:10px;border-radius:50%;background:currentColor;box-shadow:0 0 0 3px color-mix(in srgb,currentColor 16%,transparent);cursor:default;transition:transform var(--t-state),box-shadow var(--t-state)}.status-dot:hover,.status-dot:focus-visible{transform:scale(1.18);box-shadow:0 0 0 4px color-mix(in srgb,currentColor 26%,transparent);outline:none}.status-dot--draft{color:var(--color-aeblack-500, var(--ink-3))}.status-dot--review{color:var(--color-aeorange-700, var(--warning-fg))}.status-dot--approved{color:var(--color-aegreen-800, var(--success))}.status-dot--published{color:var(--color-techblue-800, var(--st-approved))}.status-dot--archived{color:var(--color-aeblack-500, var(--ink-3))}.status-dot--planned{color:var(--color-techblue-800, var(--st-approved))}.status-dot--inprogress{color:var(--color-aeorange-700, var(--warning-fg))}.status-dot--completed{color:var(--color-aegreen-800, var(--success))}.status-dot--onhold{color:var(--color-aeorange-800, var(--warning-fg))}.status-dot--cancelled{color:var(--color-aered-800, var(--danger))}.status-badge--draft{background:var(--color-aeblack-100, var(--st-draft-bg));color:var(--color-aeblack-700, var(--st-draft))}.status-badge--review{background:var(--color-aeorange-100, var(--warning-bg));color:var(--color-aeorange-700, var(--warning-fg))}.status-badge--approved{background:var(--color-aegreen-100, var(--success-bg));color:var(--color-aegreen-800, var(--success))}.status-badge--published{background:var(--color-techblue-100, var(--st-approved-bg));color:var(--color-techblue-800, var(--st-approved))}.status-badge--archived{background:var(--color-aeblack-100, var(--st-draft-bg));color:var(--color-aeblack-500, var(--ink-3))}.status-badge--planned{background:var(--color-techblue-100, var(--st-approved-bg));color:var(--color-techblue-800, var(--st-approved))}.status-badge--inprogress{background:var(--color-aeorange-100, var(--warning-bg));color:var(--color-aeorange-700, var(--warning-fg))}.status-badge--completed{background:var(--color-aegreen-100, var(--success-bg));color:var(--color-aegreen-800, var(--success))}.status-badge--onhold{background:var(--color-aeorange-200, var(--warning-bg));color:var(--color-aeorange-800, var(--warning-fg))}.status-badge--cancelled{background:var(--color-aered-100, var(--danger-bg));color:var(--color-aered-800, var(--danger))}\n"] }]
17527
18009
  }], propDecorators: { status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: true }] }], dot: [{ type: i0.Input, args: [{ isSignal: true, alias: "dot", required: false }] }], domain: [{ type: i0.Input, args: [{ isSignal: true, alias: "domain", required: false }] }] } });
17528
18010
 
17529
18011
  // Class name and selector kept as `DashboardKpiComponent` / `fly-dashboard-kpi`
@@ -18023,11 +18505,11 @@ class FlyFilterPanelComponent {
18023
18505
  /** Whether the `[filter-more]` advanced fields are revealed. */
18024
18506
  showMore = signal(false, ...(ngDevMode ? [{ debugName: "showMore" }] : /* istanbul ignore next */ []));
18025
18507
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyFilterPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
18026
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyFilterPanelComponent, isStandalone: true, selector: "fly-filter-panel", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: true, transformFunction: null }, titleKey: { classPropertyName: "titleKey", publicName: "titleKey", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, hasMore: { classPropertyName: "hasMore", publicName: "hasMore", isSignal: true, isRequired: false, transformFunction: null }, showSearchButton: { classPropertyName: "showSearchButton", publicName: "showSearchButton", isSignal: true, isRequired: false, transformFunction: null }, anchored: { classPropertyName: "anchored", publicName: "anchored", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cleared: "cleared", closed: "closed", searched: "searched" }, host: { properties: { "class.fp--anchored": "anchored()" } }, ngImport: i0, template: " @if (open()) {\r\n <div class=\"fp\" [attr.data-layout]=\"layout()\">\r\n <div class=\"fp__head\">\r\n <div class=\"fp__title\">{{ titleKey() | translate }}</div>\r\n <div class=\"fp__actions\">\r\n <button type=\"button\" class=\"fp__clear\" (click)=\"cleared.emit()\">\r\n {{ 'ui.action.clearFilters' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fp__close\"\r\n (click)=\"closed.emit()\"\r\n [attr.aria-label]=\"'ui.action.close' | translate\"\r\n >\u00D7</button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"fp__body\">\r\n <ng-content select=\"[filter-status]\" />\r\n\r\n <div class=\"fp__grid\">\r\n <ng-content />\r\n </div>\r\n\r\n @if (hasMore()) {\r\n <div class=\"fp__more\" [hidden]=\"!showMore()\">\r\n <div class=\"fp__grid\">\r\n <ng-content select=\"[filter-more]\" />\r\n </div>\r\n </div>\r\n <div class=\"fp__morebar\">\r\n <button\r\n type=\"button\"\r\n class=\"fp__morebtn\"\r\n (click)=\"showMore.set(!showMore())\"\r\n [attr.aria-expanded]=\"showMore()\"\r\n >\r\n <svg class=\"fp__moreico\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <path [attr.d]=\"showMore() ? 'M3.5 8h9' : 'M8 3.5v9M3.5 8h9'\" />\r\n </svg>\r\n {{ (showMore() ? 'ui.action.fewerOptions' : 'ui.action.addMoreOptions') | translate }}\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (showSearchButton()) {\r\n <div class=\"fp__footer\">\r\n <button type=\"button\" fly-button variant=\"primary\" (click)=\"searched.emit()\">\r\n {{ 'ui.action.search' | translate }}\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n }\r\n \r\n", styles: [":host{display:block}.fp{margin-block-end:var(--sp-4);background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-lg);box-shadow:var(--shadow-panel);overflow:hidden;animation:fly-fp-in var(--t-overlay)}:host(.fp--anchored){position:absolute;inset-block-start:calc(100% + var(--sp-2));inset-inline-end:0;z-index:var(--z-overlay);width:min(600px,100vw - 2 * var(--sp-4))}:host(.fp--anchored) .fp{margin-block-end:0;box-shadow:var(--shadow-filter)}:host(.fp--anchored) .fp__body{max-height:min(320px,44vh);overflow-y:auto}.fp__head{display:flex;align-items:center;justify-content:space-between;padding:var(--sp-3) var(--sp-4);border-block-end:1px solid var(--line-3);background:var(--bg-3)}.fp__title{font-size:var(--text-xs);font-weight:var(--fw-medium);letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3)}.fp__actions{display:flex;align-items:center;gap:var(--sp-1)}.fp__clear{border:0;background:transparent;font-size:12px;color:var(--ink-3);padding:var(--sp-1) var(--sp-2);border-radius:var(--r-sm);cursor:pointer;font-family:inherit;transition:background var(--t-state),color var(--t-state)}.fp__clear:hover{color:var(--ink);background:var(--bg-hover)}.fp__clear:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fp__close{width:24px;height:24px;border:0;background:transparent;color:var(--ink-3);cursor:pointer;border-radius:var(--r-sm);font-size:var(--text-xl);line-height:1;display:grid;place-items:center}.fp__close:hover{color:var(--ink);background:var(--bg-hover)}.fp__close:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fp ::ng-deep [filter-status]{display:flex;flex-direction:column;gap:var(--sp-2);padding:14px var(--sp-4);border-block-end:1px solid var(--line-3)}.fp__grid{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:14px var(--sp-4);padding:var(--sp-4)}@media(width<=1180px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(4,minmax(0,1fr))}}@media(width<=820px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(2,minmax(0,1fr))}}.fp[data-layout=rows] .fp__grid{grid-template-columns:1fr;gap:var(--sp-2)}.fp[data-layout=rows] ::ng-deep .fld{display:grid;grid-template-columns:minmax(110px,168px) 1fr;align-items:center;gap:var(--sp-1) var(--sp-4)}.fp[data-layout=rows] ::ng-deep .fld__l{margin:0;color:var(--ink-2)}.fp[data-layout=rows] ::ng-deep .fld__hint,.fp[data-layout=rows] ::ng-deep .fld__err{grid-column:2}@media(width<=640px){.fp[data-layout=rows] ::ng-deep .fld{grid-template-columns:1fr}.fp[data-layout=rows] ::ng-deep .fld__hint,.fp[data-layout=rows] ::ng-deep .fld__err{grid-column:1}}.fp__more{border-block-start:1px solid var(--line-3)}.fp__more .fp__grid{padding-block-start:var(--sp-3)}.fp__morebar{padding:0 var(--sp-4) var(--sp-3)}.fp__morebtn{display:inline-flex;align-items:center;gap:var(--sp-1);border:0;background:transparent;padding:var(--sp-1) var(--sp-2);border-radius:var(--r-sm);font-family:inherit;font-size:var(--text-sm);font-weight:var(--fw-medium);color:var(--accent);cursor:pointer;transition:background var(--t-state)}.fp__morebtn:hover{background:var(--bg-hover)}.fp__morebtn:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fp__moreico{width:14px;height:14px;flex-shrink:0}.fp__footer{display:flex;justify-content:flex-end;padding:var(--sp-3) var(--sp-4);border-block-start:1px solid var(--line-3);background:var(--bg-3)}.fp__footer button{background:var(--grad);border:0;color:var(--ink-inverse);box-shadow:var(--shadow-raised)}.fp__footer button:hover{background:var(--grad);border:0;filter:brightness(1.05);box-shadow:var(--shadow-accent)}@keyframes fly-fp-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}\n"], dependencies: [{ kind: "component", type: FlyButtonComponent, selector: "button[fly-button], a[fly-button]", inputs: ["variant", "size", "loading"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
18508
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyFilterPanelComponent, isStandalone: true, selector: "fly-filter-panel", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: true, transformFunction: null }, titleKey: { classPropertyName: "titleKey", publicName: "titleKey", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, hasMore: { classPropertyName: "hasMore", publicName: "hasMore", isSignal: true, isRequired: false, transformFunction: null }, showSearchButton: { classPropertyName: "showSearchButton", publicName: "showSearchButton", isSignal: true, isRequired: false, transformFunction: null }, anchored: { classPropertyName: "anchored", publicName: "anchored", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cleared: "cleared", closed: "closed", searched: "searched" }, host: { properties: { "class.fp--anchored": "anchored()" } }, ngImport: i0, template: " @if (open()) {\r\n <div class=\"fp\" [attr.data-layout]=\"layout()\">\r\n <div class=\"fp__head\">\r\n <div class=\"fp__title\">{{ titleKey() | translate }}</div>\r\n <div class=\"fp__actions\">\r\n <button type=\"button\" class=\"fp__clear\" (click)=\"cleared.emit()\">\r\n {{ 'ui.action.clearFilters' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fp__close\"\r\n (click)=\"closed.emit()\"\r\n [attr.aria-label]=\"'ui.action.close' | translate\"\r\n >\u00D7</button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"fp__body\">\r\n <ng-content select=\"[filter-status]\" />\r\n\r\n <div class=\"fp__grid\">\r\n <ng-content />\r\n </div>\r\n\r\n @if (hasMore()) {\r\n <div class=\"fp__more\" [hidden]=\"!showMore()\">\r\n <div class=\"fp__grid\">\r\n <ng-content select=\"[filter-more]\" />\r\n </div>\r\n </div>\r\n <div class=\"fp__morebar\">\r\n <button\r\n type=\"button\"\r\n class=\"fp__morebtn\"\r\n (click)=\"showMore.set(!showMore())\"\r\n [attr.aria-expanded]=\"showMore()\"\r\n >\r\n <svg class=\"fp__moreico\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <path [attr.d]=\"showMore() ? 'M3.5 8h9' : 'M8 3.5v9M3.5 8h9'\" />\r\n </svg>\r\n {{ (showMore() ? 'ui.action.fewerOptions' : 'ui.action.addMoreOptions') | translate }}\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (showSearchButton()) {\r\n <div class=\"fp__footer\">\r\n <button type=\"button\" fly-button variant=\"primary\" (click)=\"searched.emit()\">\r\n {{ 'ui.action.search' | translate }}\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n }\r\n \r\n", styles: ["@charset \"UTF-8\";:host{display:block;container-type:inline-size}.fp{margin-block-end:var(--sp-4);background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-lg);box-shadow:var(--shadow-panel);overflow:hidden;animation:fly-fp-in var(--t-overlay)}:host(.fp--anchored){position:absolute;inset-block-start:calc(100% + var(--sp-2));inset-inline-end:0;z-index:var(--z-overlay);width:min(600px,100vw - 2 * var(--sp-4))}:host(.fp--anchored) .fp{margin-block-end:0;box-shadow:var(--shadow-filter)}:host(.fp--anchored) .fp__body{max-height:min(320px,44vh);overflow-y:auto}.fp__head{display:flex;align-items:center;justify-content:space-between;padding:var(--sp-3) var(--sp-4);border-block-end:1px solid var(--line-3);background:var(--bg-3)}.fp__title{font-size:var(--text-xs);font-weight:var(--fw-medium);letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3)}.fp__actions{display:flex;align-items:center;gap:var(--sp-1)}.fp__clear{border:0;background:transparent;font-size:12px;color:var(--ink-3);padding:var(--sp-1) var(--sp-2);border-radius:var(--r-sm);cursor:pointer;font-family:inherit;transition:background var(--t-state),color var(--t-state)}.fp__clear:hover{color:var(--ink);background:var(--bg-hover)}.fp__clear:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fp__close{width:24px;height:24px;border:0;background:transparent;color:var(--ink-3);cursor:pointer;border-radius:var(--r-sm);font-size:var(--text-xl);line-height:1;display:grid;place-items:center}.fp__close:hover{color:var(--ink);background:var(--bg-hover)}.fp__close:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fp ::ng-deep [filter-status]{display:flex;flex-direction:column;gap:var(--sp-2);padding:14px var(--sp-4);border-block-end:1px solid var(--line-3)}.fp__grid{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:14px var(--sp-4);padding:var(--sp-4)}@container (width <= 1180px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(4,minmax(0,1fr))}}@media(width<=1180px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(4,minmax(0,1fr))}}@container (width <= 820px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(width<=820px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(2,minmax(0,1fr))}}.fp[data-layout=rows] .fp__grid{grid-template-columns:1fr;gap:var(--sp-2)}.fp[data-layout=rows] ::ng-deep .fld{display:grid;grid-template-columns:minmax(110px,168px) 1fr;align-items:center;gap:var(--sp-1) var(--sp-4)}.fp[data-layout=rows] ::ng-deep .fld__l{margin:0;color:var(--ink-2)}.fp[data-layout=rows] ::ng-deep .fld__hint,.fp[data-layout=rows] ::ng-deep .fld__err{grid-column:2}@container (width <= 640px){.fp[data-layout=rows] ::ng-deep .fld{grid-template-columns:1fr}.fp[data-layout=rows] ::ng-deep .fld__hint,.fp[data-layout=rows] ::ng-deep .fld__err{grid-column:1}}@media(width<=640px){.fp[data-layout=rows] ::ng-deep .fld{grid-template-columns:1fr}.fp[data-layout=rows] ::ng-deep .fld__hint,.fp[data-layout=rows] ::ng-deep .fld__err{grid-column:1}}.fp__more{border-block-start:1px solid var(--line-3)}.fp__more .fp__grid{padding-block-start:var(--sp-3)}.fp__morebar{padding:0 var(--sp-4) var(--sp-3)}.fp__morebtn{display:inline-flex;align-items:center;gap:var(--sp-1);border:0;background:transparent;padding:var(--sp-1) var(--sp-2);border-radius:var(--r-sm);font-family:inherit;font-size:var(--text-sm);font-weight:var(--fw-medium);color:var(--accent);cursor:pointer;transition:background var(--t-state)}.fp__morebtn:hover{background:var(--bg-hover)}.fp__morebtn:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fp__moreico{width:14px;height:14px;flex-shrink:0}.fp__footer{display:flex;justify-content:flex-end;padding:var(--sp-3) var(--sp-4);border-block-start:1px solid var(--line-3);background:var(--bg-3)}.fp__footer button{background:var(--grad);border:0;color:var(--ink-inverse);box-shadow:var(--shadow-raised)}.fp__footer button:hover{background:var(--grad);border:0;filter:brightness(1.05);box-shadow:var(--shadow-accent)}@keyframes fly-fp-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}\n"], dependencies: [{ kind: "component", type: FlyButtonComponent, selector: "button[fly-button], a[fly-button]", inputs: ["variant", "size", "loading"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
18027
18509
  }
18028
18510
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyFilterPanelComponent, decorators: [{
18029
18511
  type: Component,
18030
- args: [{ selector: 'fly-filter-panel', standalone: true, imports: [TranslatePipe, FlyButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: { '[class.fp--anchored]': 'anchored()' }, template: " @if (open()) {\r\n <div class=\"fp\" [attr.data-layout]=\"layout()\">\r\n <div class=\"fp__head\">\r\n <div class=\"fp__title\">{{ titleKey() | translate }}</div>\r\n <div class=\"fp__actions\">\r\n <button type=\"button\" class=\"fp__clear\" (click)=\"cleared.emit()\">\r\n {{ 'ui.action.clearFilters' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fp__close\"\r\n (click)=\"closed.emit()\"\r\n [attr.aria-label]=\"'ui.action.close' | translate\"\r\n >\u00D7</button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"fp__body\">\r\n <ng-content select=\"[filter-status]\" />\r\n\r\n <div class=\"fp__grid\">\r\n <ng-content />\r\n </div>\r\n\r\n @if (hasMore()) {\r\n <div class=\"fp__more\" [hidden]=\"!showMore()\">\r\n <div class=\"fp__grid\">\r\n <ng-content select=\"[filter-more]\" />\r\n </div>\r\n </div>\r\n <div class=\"fp__morebar\">\r\n <button\r\n type=\"button\"\r\n class=\"fp__morebtn\"\r\n (click)=\"showMore.set(!showMore())\"\r\n [attr.aria-expanded]=\"showMore()\"\r\n >\r\n <svg class=\"fp__moreico\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <path [attr.d]=\"showMore() ? 'M3.5 8h9' : 'M8 3.5v9M3.5 8h9'\" />\r\n </svg>\r\n {{ (showMore() ? 'ui.action.fewerOptions' : 'ui.action.addMoreOptions') | translate }}\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (showSearchButton()) {\r\n <div class=\"fp__footer\">\r\n <button type=\"button\" fly-button variant=\"primary\" (click)=\"searched.emit()\">\r\n {{ 'ui.action.search' | translate }}\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n }\r\n \r\n", styles: [":host{display:block}.fp{margin-block-end:var(--sp-4);background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-lg);box-shadow:var(--shadow-panel);overflow:hidden;animation:fly-fp-in var(--t-overlay)}:host(.fp--anchored){position:absolute;inset-block-start:calc(100% + var(--sp-2));inset-inline-end:0;z-index:var(--z-overlay);width:min(600px,100vw - 2 * var(--sp-4))}:host(.fp--anchored) .fp{margin-block-end:0;box-shadow:var(--shadow-filter)}:host(.fp--anchored) .fp__body{max-height:min(320px,44vh);overflow-y:auto}.fp__head{display:flex;align-items:center;justify-content:space-between;padding:var(--sp-3) var(--sp-4);border-block-end:1px solid var(--line-3);background:var(--bg-3)}.fp__title{font-size:var(--text-xs);font-weight:var(--fw-medium);letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3)}.fp__actions{display:flex;align-items:center;gap:var(--sp-1)}.fp__clear{border:0;background:transparent;font-size:12px;color:var(--ink-3);padding:var(--sp-1) var(--sp-2);border-radius:var(--r-sm);cursor:pointer;font-family:inherit;transition:background var(--t-state),color var(--t-state)}.fp__clear:hover{color:var(--ink);background:var(--bg-hover)}.fp__clear:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fp__close{width:24px;height:24px;border:0;background:transparent;color:var(--ink-3);cursor:pointer;border-radius:var(--r-sm);font-size:var(--text-xl);line-height:1;display:grid;place-items:center}.fp__close:hover{color:var(--ink);background:var(--bg-hover)}.fp__close:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fp ::ng-deep [filter-status]{display:flex;flex-direction:column;gap:var(--sp-2);padding:14px var(--sp-4);border-block-end:1px solid var(--line-3)}.fp__grid{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:14px var(--sp-4);padding:var(--sp-4)}@media(width<=1180px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(4,minmax(0,1fr))}}@media(width<=820px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(2,minmax(0,1fr))}}.fp[data-layout=rows] .fp__grid{grid-template-columns:1fr;gap:var(--sp-2)}.fp[data-layout=rows] ::ng-deep .fld{display:grid;grid-template-columns:minmax(110px,168px) 1fr;align-items:center;gap:var(--sp-1) var(--sp-4)}.fp[data-layout=rows] ::ng-deep .fld__l{margin:0;color:var(--ink-2)}.fp[data-layout=rows] ::ng-deep .fld__hint,.fp[data-layout=rows] ::ng-deep .fld__err{grid-column:2}@media(width<=640px){.fp[data-layout=rows] ::ng-deep .fld{grid-template-columns:1fr}.fp[data-layout=rows] ::ng-deep .fld__hint,.fp[data-layout=rows] ::ng-deep .fld__err{grid-column:1}}.fp__more{border-block-start:1px solid var(--line-3)}.fp__more .fp__grid{padding-block-start:var(--sp-3)}.fp__morebar{padding:0 var(--sp-4) var(--sp-3)}.fp__morebtn{display:inline-flex;align-items:center;gap:var(--sp-1);border:0;background:transparent;padding:var(--sp-1) var(--sp-2);border-radius:var(--r-sm);font-family:inherit;font-size:var(--text-sm);font-weight:var(--fw-medium);color:var(--accent);cursor:pointer;transition:background var(--t-state)}.fp__morebtn:hover{background:var(--bg-hover)}.fp__morebtn:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fp__moreico{width:14px;height:14px;flex-shrink:0}.fp__footer{display:flex;justify-content:flex-end;padding:var(--sp-3) var(--sp-4);border-block-start:1px solid var(--line-3);background:var(--bg-3)}.fp__footer button{background:var(--grad);border:0;color:var(--ink-inverse);box-shadow:var(--shadow-raised)}.fp__footer button:hover{background:var(--grad);border:0;filter:brightness(1.05);box-shadow:var(--shadow-accent)}@keyframes fly-fp-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}\n"] }]
18512
+ args: [{ selector: 'fly-filter-panel', standalone: true, imports: [TranslatePipe, FlyButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: { '[class.fp--anchored]': 'anchored()' }, template: " @if (open()) {\r\n <div class=\"fp\" [attr.data-layout]=\"layout()\">\r\n <div class=\"fp__head\">\r\n <div class=\"fp__title\">{{ titleKey() | translate }}</div>\r\n <div class=\"fp__actions\">\r\n <button type=\"button\" class=\"fp__clear\" (click)=\"cleared.emit()\">\r\n {{ 'ui.action.clearFilters' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fp__close\"\r\n (click)=\"closed.emit()\"\r\n [attr.aria-label]=\"'ui.action.close' | translate\"\r\n >\u00D7</button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"fp__body\">\r\n <ng-content select=\"[filter-status]\" />\r\n\r\n <div class=\"fp__grid\">\r\n <ng-content />\r\n </div>\r\n\r\n @if (hasMore()) {\r\n <div class=\"fp__more\" [hidden]=\"!showMore()\">\r\n <div class=\"fp__grid\">\r\n <ng-content select=\"[filter-more]\" />\r\n </div>\r\n </div>\r\n <div class=\"fp__morebar\">\r\n <button\r\n type=\"button\"\r\n class=\"fp__morebtn\"\r\n (click)=\"showMore.set(!showMore())\"\r\n [attr.aria-expanded]=\"showMore()\"\r\n >\r\n <svg class=\"fp__moreico\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <path [attr.d]=\"showMore() ? 'M3.5 8h9' : 'M8 3.5v9M3.5 8h9'\" />\r\n </svg>\r\n {{ (showMore() ? 'ui.action.fewerOptions' : 'ui.action.addMoreOptions') | translate }}\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (showSearchButton()) {\r\n <div class=\"fp__footer\">\r\n <button type=\"button\" fly-button variant=\"primary\" (click)=\"searched.emit()\">\r\n {{ 'ui.action.search' | translate }}\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n }\r\n \r\n", styles: ["@charset \"UTF-8\";:host{display:block;container-type:inline-size}.fp{margin-block-end:var(--sp-4);background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-lg);box-shadow:var(--shadow-panel);overflow:hidden;animation:fly-fp-in var(--t-overlay)}:host(.fp--anchored){position:absolute;inset-block-start:calc(100% + var(--sp-2));inset-inline-end:0;z-index:var(--z-overlay);width:min(600px,100vw - 2 * var(--sp-4))}:host(.fp--anchored) .fp{margin-block-end:0;box-shadow:var(--shadow-filter)}:host(.fp--anchored) .fp__body{max-height:min(320px,44vh);overflow-y:auto}.fp__head{display:flex;align-items:center;justify-content:space-between;padding:var(--sp-3) var(--sp-4);border-block-end:1px solid var(--line-3);background:var(--bg-3)}.fp__title{font-size:var(--text-xs);font-weight:var(--fw-medium);letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3)}.fp__actions{display:flex;align-items:center;gap:var(--sp-1)}.fp__clear{border:0;background:transparent;font-size:12px;color:var(--ink-3);padding:var(--sp-1) var(--sp-2);border-radius:var(--r-sm);cursor:pointer;font-family:inherit;transition:background var(--t-state),color var(--t-state)}.fp__clear:hover{color:var(--ink);background:var(--bg-hover)}.fp__clear:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fp__close{width:24px;height:24px;border:0;background:transparent;color:var(--ink-3);cursor:pointer;border-radius:var(--r-sm);font-size:var(--text-xl);line-height:1;display:grid;place-items:center}.fp__close:hover{color:var(--ink);background:var(--bg-hover)}.fp__close:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fp ::ng-deep [filter-status]{display:flex;flex-direction:column;gap:var(--sp-2);padding:14px var(--sp-4);border-block-end:1px solid var(--line-3)}.fp__grid{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:14px var(--sp-4);padding:var(--sp-4)}@container (width <= 1180px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(4,minmax(0,1fr))}}@media(width<=1180px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(4,minmax(0,1fr))}}@container (width <= 820px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(width<=820px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(2,minmax(0,1fr))}}.fp[data-layout=rows] .fp__grid{grid-template-columns:1fr;gap:var(--sp-2)}.fp[data-layout=rows] ::ng-deep .fld{display:grid;grid-template-columns:minmax(110px,168px) 1fr;align-items:center;gap:var(--sp-1) var(--sp-4)}.fp[data-layout=rows] ::ng-deep .fld__l{margin:0;color:var(--ink-2)}.fp[data-layout=rows] ::ng-deep .fld__hint,.fp[data-layout=rows] ::ng-deep .fld__err{grid-column:2}@container (width <= 640px){.fp[data-layout=rows] ::ng-deep .fld{grid-template-columns:1fr}.fp[data-layout=rows] ::ng-deep .fld__hint,.fp[data-layout=rows] ::ng-deep .fld__err{grid-column:1}}@media(width<=640px){.fp[data-layout=rows] ::ng-deep .fld{grid-template-columns:1fr}.fp[data-layout=rows] ::ng-deep .fld__hint,.fp[data-layout=rows] ::ng-deep .fld__err{grid-column:1}}.fp__more{border-block-start:1px solid var(--line-3)}.fp__more .fp__grid{padding-block-start:var(--sp-3)}.fp__morebar{padding:0 var(--sp-4) var(--sp-3)}.fp__morebtn{display:inline-flex;align-items:center;gap:var(--sp-1);border:0;background:transparent;padding:var(--sp-1) var(--sp-2);border-radius:var(--r-sm);font-family:inherit;font-size:var(--text-sm);font-weight:var(--fw-medium);color:var(--accent);cursor:pointer;transition:background var(--t-state)}.fp__morebtn:hover{background:var(--bg-hover)}.fp__morebtn:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fp__moreico{width:14px;height:14px;flex-shrink:0}.fp__footer{display:flex;justify-content:flex-end;padding:var(--sp-3) var(--sp-4);border-block-start:1px solid var(--line-3);background:var(--bg-3)}.fp__footer button{background:var(--grad);border:0;color:var(--ink-inverse);box-shadow:var(--shadow-raised)}.fp__footer button:hover{background:var(--grad);border:0;filter:brightness(1.05);box-shadow:var(--shadow-accent)}@keyframes fly-fp-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}\n"] }]
18031
18513
  }], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: true }] }], titleKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleKey", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], hasMore: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasMore", required: false }] }], showSearchButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSearchButton", required: false }] }], anchored: [{ type: i0.Input, args: [{ isSignal: true, alias: "anchored", required: false }] }], cleared: [{ type: i0.Output, args: ["cleared"] }], closed: [{ type: i0.Output, args: ["closed"] }], searched: [{ type: i0.Output, args: ["searched"] }] } });
18032
18514
 
18033
18515
  /**
@@ -18167,11 +18649,11 @@ class FlyDataTableComponent {
18167
18649
  this.sortChange.emit(nextSort(this.sort(), col.id));
18168
18650
  }
18169
18651
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyDataTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
18170
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyDataTableComponent, isStandalone: true, selector: "fly-data-table", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: true, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelKey: { classPropertyName: "ariaLabelKey", publicName: "ariaLabelKey", isSignal: true, isRequired: false, transformFunction: null }, sort: { classPropertyName: "sort", publicName: "sort", isSignal: true, isRequired: false, transformFunction: null }, emptyKey: { classPropertyName: "emptyKey", publicName: "emptyKey", isSignal: true, isRequired: false, transformFunction: null }, emptyIconClass: { classPropertyName: "emptyIconClass", publicName: "emptyIconClass", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sortChange: "sortChange", rowActivated: "rowActivated" }, queries: [{ propertyName: "cellTemplates", predicate: FlyCellDirective, isSignal: true }], ngImport: i0, template: "<table class=\"dt\" [attr.aria-label]=\"ariaLabelKey() ? (ariaLabelKey()! | translate) : null\">\r\n <thead>\r\n <tr>\r\n @for (col of columns(); track col.id) {\r\n <th\r\n scope=\"col\"\r\n [class.dt__th--end]=\"col.align === 'end'\"\r\n [attr.aria-sort]=\"headerAriaSort(col)\"\r\n >\r\n @if (col.sortable) {\r\n <button type=\"button\" class=\"dt__sort\" (click)=\"onHeaderClick(col)\">\r\n <span>{{ col.headerKey | translate }}</span>\r\n <span class=\"dt__sort-icon\" [attr.data-dir]=\"sortDirFor(col)\" aria-hidden=\"true\"></span>\r\n </button>\r\n } @else {\r\n {{ col.headerKey | translate }}\r\n }\r\n </th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @if (loading()) {\r\n @for (i of skeletonRows; track i) {\r\n <tr class=\"dt__row dt__row--skel\">\r\n <td [attr.colspan]=\"columns().length\">\r\n <!-- shape=\"lines\" is the DS skeleton's no-chrome text shimmer\r\n (the kit's former shape=\"text\"); one line per collapsed row.\r\n No backticks in here: this is an INLINE template literal. -->\r\n <fly-skeleton shape=\"lines\" [lines]=\"1\" />\r\n </td>\r\n </tr>\r\n }\r\n } @else {\r\n @for (row of rows(); track $index) {\r\n <tr\r\n class=\"dt__row\"\r\n tabindex=\"0\"\r\n (click)=\"onRowActivate($event, row)\"\r\n (keydown.enter)=\"onRowActivate($event, row)\"\r\n >\r\n @for (col of columns(); track col.id) {\r\n <td\r\n [class.dt__td--end]=\"col.align === 'end'\"\r\n [class.mono]=\"col.kind === 'mono'\"\r\n [class.dt__td--date]=\"col.kind === 'date'\"\r\n [class.dt__td--num]=\"col.kind === 'number'\"\r\n [attr.data-label]=\"col.headerKey | translate\"\r\n >\r\n @if (templateFor(col.id); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: row }\" />\r\n } @else {\r\n @switch (col.kind ?? 'text') {\r\n @case ('date') {\r\n @if (cellValue(row, col); as value) {\r\n {{ $any(value) | date: 'yyyy-MM-dd' : 'UTC' }}\r\n } @else { \u2014 }\r\n }\r\n @default {\r\n {{ displayValue(row, col) }}\r\n }\r\n }\r\n }\r\n </td>\r\n }\r\n </tr>\r\n }\r\n }\r\n </tbody>\r\n</table>\r\n@if (!loading() && rows().length === 0 && emptyKey()) {\r\n <fly-state-message kind=\"empty\" [messageKey]=\"emptyKey()\" [iconClass]=\"emptyIconClass()\" />\r\n}\r\n", styles: ["@charset \"UTF-8\";:host{display:block;background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-lg);overflow:hidden;box-shadow:var(--shadow-card)}.dt{width:100%;border-collapse:collapse;font-size:var(--text-base)}.dt thead th{text-align:start;padding:var(--sp-2) var(--sp-3);font-size:12px;font-weight:var(--fw-bold);text-transform:uppercase;letter-spacing:.03em;color:var(--ink-3);background:var(--bg-3);border-block-end:1px solid var(--line)}.dt__th--end{text-align:end}.dt__sort{display:inline-flex;align-items:center;gap:var(--sp-1);padding:0;border:0;background:transparent;font:inherit;letter-spacing:inherit;text-transform:inherit;color:inherit;cursor:pointer}.dt__sort:hover{color:var(--ink)}.dt__sort:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.dt__sort-icon{inline-size:0;block-size:0;border-inline:4px solid transparent;border-block-start:5px solid var(--ink-4);opacity:.45;transition:transform var(--t-state),opacity var(--t-state)}.dt__sort-icon[data-dir=asc]{opacity:1;border-block-start-color:var(--accent);transform:rotate(180deg)}.dt__sort-icon[data-dir=desc]{opacity:1;border-block-start-color:var(--accent)}.dt tbody td{height:var(--row-h);padding:var(--sp-2) var(--sp-3);border-block-end:1px solid var(--line-3);color:var(--ink);transition:background var(--t-state)}.dt tbody tr:last-child td{border-block-end:0}.dt__row{cursor:pointer}.dt__row:hover td{background:var(--bg-hover)}.dt__row:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px;outline-offset:-2px}.dt__row--skel{cursor:default}.dt__row--skel:hover td{background:transparent}.dt__td--end{text-align:end}.dt__td--date,.dt__td--num{color:var(--ink-3);font-size:var(--text-sm);font-variant-numeric:tabular-nums}@media(width<=720px){.dt thead{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.dt,.dt tbody,.dt tbody tr,.dt tbody td{display:block}.dt__row{padding:var(--sp-2) 0;border-block-end:1px solid var(--line-3)}.dt__row:last-child{border-block-end:0}.dt tbody td{display:flex;align-items:baseline;justify-content:space-between;gap:var(--sp-3);height:auto;border-block-end:0;padding:var(--sp-1) var(--sp-3);text-align:end}.dt tbody td:before{content:attr(data-label);flex-shrink:0;font-size:var(--text-xs);font-weight:var(--fw-medium);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-4);text-align:start}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: FlyStateMessageComponent, selector: "fly-state-message", inputs: ["kind", "messageKey", "iconClass", "retryLabelKey", "showRetry"], outputs: ["retried"] }, { kind: "component", type: FlySkeletonComponent, selector: "fly-skeleton", inputs: ["layout", "shape", "count", "lines", "animation", "fill", "messageKey", "announce"] }, { kind: "pipe", type: DatePipe, name: "date" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
18652
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyDataTableComponent, isStandalone: true, selector: "fly-data-table", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: true, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelKey: { classPropertyName: "ariaLabelKey", publicName: "ariaLabelKey", isSignal: true, isRequired: false, transformFunction: null }, sort: { classPropertyName: "sort", publicName: "sort", isSignal: true, isRequired: false, transformFunction: null }, emptyKey: { classPropertyName: "emptyKey", publicName: "emptyKey", isSignal: true, isRequired: false, transformFunction: null }, emptyIconClass: { classPropertyName: "emptyIconClass", publicName: "emptyIconClass", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sortChange: "sortChange", rowActivated: "rowActivated" }, queries: [{ propertyName: "cellTemplates", predicate: FlyCellDirective, isSignal: true }], ngImport: i0, template: "<table class=\"dt\" [attr.aria-label]=\"ariaLabelKey() ? (ariaLabelKey()! | translate) : null\">\r\n <thead>\r\n <tr>\r\n @for (col of columns(); track col.id) {\r\n <th\r\n scope=\"col\"\r\n [class.dt__th--end]=\"col.align === 'end'\"\r\n [attr.aria-sort]=\"headerAriaSort(col)\"\r\n >\r\n @if (col.sortable) {\r\n <button type=\"button\" class=\"dt__sort\" (click)=\"onHeaderClick(col)\">\r\n <span>{{ col.headerKey | translate }}</span>\r\n <span class=\"dt__sort-icon\" [attr.data-dir]=\"sortDirFor(col)\" aria-hidden=\"true\"></span>\r\n </button>\r\n } @else {\r\n {{ col.headerKey | translate }}\r\n }\r\n </th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @if (loading()) {\r\n @for (i of skeletonRows; track i) {\r\n <tr class=\"dt__row dt__row--skel\">\r\n <td [attr.colspan]=\"columns().length\">\r\n <!-- shape=\"lines\" is the DS skeleton's no-chrome text shimmer\r\n (the kit's former shape=\"text\"); one line per collapsed row.\r\n No backticks in here: this is an INLINE template literal. -->\r\n <fly-skeleton shape=\"lines\" [lines]=\"1\" />\r\n </td>\r\n </tr>\r\n }\r\n } @else {\r\n @for (row of rows(); track $index) {\r\n <tr\r\n class=\"dt__row\"\r\n tabindex=\"0\"\r\n (click)=\"onRowActivate($event, row)\"\r\n (keydown.enter)=\"onRowActivate($event, row)\"\r\n >\r\n @for (col of columns(); track col.id) {\r\n <td\r\n [class.dt__td--end]=\"col.align === 'end'\"\r\n [class.mono]=\"col.kind === 'mono'\"\r\n [class.dt__td--date]=\"col.kind === 'date'\"\r\n [class.dt__td--num]=\"col.kind === 'number'\"\r\n [attr.data-label]=\"col.headerKey | translate\"\r\n >\r\n @if (templateFor(col.id); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: row }\" />\r\n } @else {\r\n @switch (col.kind ?? 'text') {\r\n @case ('date') {\r\n @if (cellValue(row, col); as value) {\r\n {{ $any(value) | date: 'yyyy-MM-dd' : 'UTC' }}\r\n } @else { \u2014 }\r\n }\r\n @default {\r\n {{ displayValue(row, col) }}\r\n }\r\n }\r\n }\r\n </td>\r\n }\r\n </tr>\r\n }\r\n }\r\n </tbody>\r\n</table>\r\n@if (!loading() && rows().length === 0 && emptyKey()) {\r\n <fly-state-message kind=\"empty\" [messageKey]=\"emptyKey()\" [iconClass]=\"emptyIconClass()\" />\r\n}\r\n", styles: ["@charset \"UTF-8\";:host{display:block;background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-lg);overflow:hidden;box-shadow:var(--shadow-card);container-type:inline-size}.dt{width:100%;border-collapse:collapse;font-size:var(--text-base)}.dt thead th{text-align:start;padding:var(--sp-2) var(--sp-3);font-size:12px;font-weight:var(--fw-bold);text-transform:uppercase;letter-spacing:.03em;color:var(--ink-3);background:var(--bg-3);border-block-end:1px solid var(--line)}.dt__th--end{text-align:end}.dt__sort{display:inline-flex;align-items:center;gap:var(--sp-1);padding:0;border:0;background:transparent;font:inherit;letter-spacing:inherit;text-transform:inherit;color:inherit;cursor:pointer}.dt__sort:hover{color:var(--ink)}.dt__sort:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.dt__sort-icon{inline-size:0;block-size:0;border-inline:4px solid transparent;border-block-start:5px solid var(--ink-4);opacity:.45;transition:transform var(--t-state),opacity var(--t-state)}.dt__sort-icon[data-dir=asc]{opacity:1;border-block-start-color:var(--accent);transform:rotate(180deg)}.dt__sort-icon[data-dir=desc]{opacity:1;border-block-start-color:var(--accent)}.dt tbody td{height:var(--row-h);padding:var(--sp-2) var(--sp-3);border-block-end:1px solid var(--line-3);color:var(--ink);transition:background var(--t-state)}.dt tbody tr:last-child td{border-block-end:0}.dt__row{cursor:pointer}.dt__row:hover td{background:var(--bg-hover)}.dt__row:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px;outline-offset:-2px}.dt__row--skel{cursor:default}.dt__row--skel:hover td{background:transparent}.dt__td--end{text-align:end}.dt__td--date,.dt__td--num{color:var(--ink-3);font-size:var(--text-sm);font-variant-numeric:tabular-nums}@container (width <= 720px){.dt thead{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.dt,.dt tbody,.dt tbody tr,.dt tbody td{display:block}.dt__row{padding:var(--sp-2) 0;border-block-end:1px solid var(--line-3)}.dt__row:last-child{border-block-end:0}.dt tbody td{display:flex;align-items:baseline;justify-content:space-between;gap:var(--sp-3);height:auto;border-block-end:0;padding:var(--sp-1) var(--sp-3);text-align:end}.dt tbody td:before{content:attr(data-label);flex-shrink:0;font-size:var(--text-xs);font-weight:var(--fw-medium);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-4);text-align:start}}@media(width<=720px){.dt thead{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.dt,.dt tbody,.dt tbody tr,.dt tbody td{display:block}.dt__row{padding:var(--sp-2) 0;border-block-end:1px solid var(--line-3)}.dt__row:last-child{border-block-end:0}.dt tbody td{display:flex;align-items:baseline;justify-content:space-between;gap:var(--sp-3);height:auto;border-block-end:0;padding:var(--sp-1) var(--sp-3);text-align:end}.dt tbody td:before{content:attr(data-label);flex-shrink:0;font-size:var(--text-xs);font-weight:var(--fw-medium);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-4);text-align:start}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: FlyStateMessageComponent, selector: "fly-state-message", inputs: ["kind", "messageKey", "iconClass", "retryLabelKey", "showRetry"], outputs: ["retried"] }, { kind: "component", type: FlySkeletonComponent, selector: "fly-skeleton", inputs: ["layout", "shape", "count", "lines", "animation", "fill", "messageKey", "announce"] }, { kind: "pipe", type: DatePipe, name: "date" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
18171
18653
  }
18172
18654
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyDataTableComponent, decorators: [{
18173
18655
  type: Component,
18174
- args: [{ selector: 'fly-data-table', standalone: true, imports: [DatePipe, NgTemplateOutlet, TranslatePipe, FlyStateMessageComponent, FlySkeletonComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<table class=\"dt\" [attr.aria-label]=\"ariaLabelKey() ? (ariaLabelKey()! | translate) : null\">\r\n <thead>\r\n <tr>\r\n @for (col of columns(); track col.id) {\r\n <th\r\n scope=\"col\"\r\n [class.dt__th--end]=\"col.align === 'end'\"\r\n [attr.aria-sort]=\"headerAriaSort(col)\"\r\n >\r\n @if (col.sortable) {\r\n <button type=\"button\" class=\"dt__sort\" (click)=\"onHeaderClick(col)\">\r\n <span>{{ col.headerKey | translate }}</span>\r\n <span class=\"dt__sort-icon\" [attr.data-dir]=\"sortDirFor(col)\" aria-hidden=\"true\"></span>\r\n </button>\r\n } @else {\r\n {{ col.headerKey | translate }}\r\n }\r\n </th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @if (loading()) {\r\n @for (i of skeletonRows; track i) {\r\n <tr class=\"dt__row dt__row--skel\">\r\n <td [attr.colspan]=\"columns().length\">\r\n <!-- shape=\"lines\" is the DS skeleton's no-chrome text shimmer\r\n (the kit's former shape=\"text\"); one line per collapsed row.\r\n No backticks in here: this is an INLINE template literal. -->\r\n <fly-skeleton shape=\"lines\" [lines]=\"1\" />\r\n </td>\r\n </tr>\r\n }\r\n } @else {\r\n @for (row of rows(); track $index) {\r\n <tr\r\n class=\"dt__row\"\r\n tabindex=\"0\"\r\n (click)=\"onRowActivate($event, row)\"\r\n (keydown.enter)=\"onRowActivate($event, row)\"\r\n >\r\n @for (col of columns(); track col.id) {\r\n <td\r\n [class.dt__td--end]=\"col.align === 'end'\"\r\n [class.mono]=\"col.kind === 'mono'\"\r\n [class.dt__td--date]=\"col.kind === 'date'\"\r\n [class.dt__td--num]=\"col.kind === 'number'\"\r\n [attr.data-label]=\"col.headerKey | translate\"\r\n >\r\n @if (templateFor(col.id); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: row }\" />\r\n } @else {\r\n @switch (col.kind ?? 'text') {\r\n @case ('date') {\r\n @if (cellValue(row, col); as value) {\r\n {{ $any(value) | date: 'yyyy-MM-dd' : 'UTC' }}\r\n } @else { \u2014 }\r\n }\r\n @default {\r\n {{ displayValue(row, col) }}\r\n }\r\n }\r\n }\r\n </td>\r\n }\r\n </tr>\r\n }\r\n }\r\n </tbody>\r\n</table>\r\n@if (!loading() && rows().length === 0 && emptyKey()) {\r\n <fly-state-message kind=\"empty\" [messageKey]=\"emptyKey()\" [iconClass]=\"emptyIconClass()\" />\r\n}\r\n", styles: ["@charset \"UTF-8\";:host{display:block;background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-lg);overflow:hidden;box-shadow:var(--shadow-card)}.dt{width:100%;border-collapse:collapse;font-size:var(--text-base)}.dt thead th{text-align:start;padding:var(--sp-2) var(--sp-3);font-size:12px;font-weight:var(--fw-bold);text-transform:uppercase;letter-spacing:.03em;color:var(--ink-3);background:var(--bg-3);border-block-end:1px solid var(--line)}.dt__th--end{text-align:end}.dt__sort{display:inline-flex;align-items:center;gap:var(--sp-1);padding:0;border:0;background:transparent;font:inherit;letter-spacing:inherit;text-transform:inherit;color:inherit;cursor:pointer}.dt__sort:hover{color:var(--ink)}.dt__sort:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.dt__sort-icon{inline-size:0;block-size:0;border-inline:4px solid transparent;border-block-start:5px solid var(--ink-4);opacity:.45;transition:transform var(--t-state),opacity var(--t-state)}.dt__sort-icon[data-dir=asc]{opacity:1;border-block-start-color:var(--accent);transform:rotate(180deg)}.dt__sort-icon[data-dir=desc]{opacity:1;border-block-start-color:var(--accent)}.dt tbody td{height:var(--row-h);padding:var(--sp-2) var(--sp-3);border-block-end:1px solid var(--line-3);color:var(--ink);transition:background var(--t-state)}.dt tbody tr:last-child td{border-block-end:0}.dt__row{cursor:pointer}.dt__row:hover td{background:var(--bg-hover)}.dt__row:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px;outline-offset:-2px}.dt__row--skel{cursor:default}.dt__row--skel:hover td{background:transparent}.dt__td--end{text-align:end}.dt__td--date,.dt__td--num{color:var(--ink-3);font-size:var(--text-sm);font-variant-numeric:tabular-nums}@media(width<=720px){.dt thead{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.dt,.dt tbody,.dt tbody tr,.dt tbody td{display:block}.dt__row{padding:var(--sp-2) 0;border-block-end:1px solid var(--line-3)}.dt__row:last-child{border-block-end:0}.dt tbody td{display:flex;align-items:baseline;justify-content:space-between;gap:var(--sp-3);height:auto;border-block-end:0;padding:var(--sp-1) var(--sp-3);text-align:end}.dt tbody td:before{content:attr(data-label);flex-shrink:0;font-size:var(--text-xs);font-weight:var(--fw-medium);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-4);text-align:start}}\n"] }]
18656
+ args: [{ selector: 'fly-data-table', standalone: true, imports: [DatePipe, NgTemplateOutlet, TranslatePipe, FlyStateMessageComponent, FlySkeletonComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<table class=\"dt\" [attr.aria-label]=\"ariaLabelKey() ? (ariaLabelKey()! | translate) : null\">\r\n <thead>\r\n <tr>\r\n @for (col of columns(); track col.id) {\r\n <th\r\n scope=\"col\"\r\n [class.dt__th--end]=\"col.align === 'end'\"\r\n [attr.aria-sort]=\"headerAriaSort(col)\"\r\n >\r\n @if (col.sortable) {\r\n <button type=\"button\" class=\"dt__sort\" (click)=\"onHeaderClick(col)\">\r\n <span>{{ col.headerKey | translate }}</span>\r\n <span class=\"dt__sort-icon\" [attr.data-dir]=\"sortDirFor(col)\" aria-hidden=\"true\"></span>\r\n </button>\r\n } @else {\r\n {{ col.headerKey | translate }}\r\n }\r\n </th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @if (loading()) {\r\n @for (i of skeletonRows; track i) {\r\n <tr class=\"dt__row dt__row--skel\">\r\n <td [attr.colspan]=\"columns().length\">\r\n <!-- shape=\"lines\" is the DS skeleton's no-chrome text shimmer\r\n (the kit's former shape=\"text\"); one line per collapsed row.\r\n No backticks in here: this is an INLINE template literal. -->\r\n <fly-skeleton shape=\"lines\" [lines]=\"1\" />\r\n </td>\r\n </tr>\r\n }\r\n } @else {\r\n @for (row of rows(); track $index) {\r\n <tr\r\n class=\"dt__row\"\r\n tabindex=\"0\"\r\n (click)=\"onRowActivate($event, row)\"\r\n (keydown.enter)=\"onRowActivate($event, row)\"\r\n >\r\n @for (col of columns(); track col.id) {\r\n <td\r\n [class.dt__td--end]=\"col.align === 'end'\"\r\n [class.mono]=\"col.kind === 'mono'\"\r\n [class.dt__td--date]=\"col.kind === 'date'\"\r\n [class.dt__td--num]=\"col.kind === 'number'\"\r\n [attr.data-label]=\"col.headerKey | translate\"\r\n >\r\n @if (templateFor(col.id); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: row }\" />\r\n } @else {\r\n @switch (col.kind ?? 'text') {\r\n @case ('date') {\r\n @if (cellValue(row, col); as value) {\r\n {{ $any(value) | date: 'yyyy-MM-dd' : 'UTC' }}\r\n } @else { \u2014 }\r\n }\r\n @default {\r\n {{ displayValue(row, col) }}\r\n }\r\n }\r\n }\r\n </td>\r\n }\r\n </tr>\r\n }\r\n }\r\n </tbody>\r\n</table>\r\n@if (!loading() && rows().length === 0 && emptyKey()) {\r\n <fly-state-message kind=\"empty\" [messageKey]=\"emptyKey()\" [iconClass]=\"emptyIconClass()\" />\r\n}\r\n", styles: ["@charset \"UTF-8\";:host{display:block;background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-lg);overflow:hidden;box-shadow:var(--shadow-card);container-type:inline-size}.dt{width:100%;border-collapse:collapse;font-size:var(--text-base)}.dt thead th{text-align:start;padding:var(--sp-2) var(--sp-3);font-size:12px;font-weight:var(--fw-bold);text-transform:uppercase;letter-spacing:.03em;color:var(--ink-3);background:var(--bg-3);border-block-end:1px solid var(--line)}.dt__th--end{text-align:end}.dt__sort{display:inline-flex;align-items:center;gap:var(--sp-1);padding:0;border:0;background:transparent;font:inherit;letter-spacing:inherit;text-transform:inherit;color:inherit;cursor:pointer}.dt__sort:hover{color:var(--ink)}.dt__sort:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.dt__sort-icon{inline-size:0;block-size:0;border-inline:4px solid transparent;border-block-start:5px solid var(--ink-4);opacity:.45;transition:transform var(--t-state),opacity var(--t-state)}.dt__sort-icon[data-dir=asc]{opacity:1;border-block-start-color:var(--accent);transform:rotate(180deg)}.dt__sort-icon[data-dir=desc]{opacity:1;border-block-start-color:var(--accent)}.dt tbody td{height:var(--row-h);padding:var(--sp-2) var(--sp-3);border-block-end:1px solid var(--line-3);color:var(--ink);transition:background var(--t-state)}.dt tbody tr:last-child td{border-block-end:0}.dt__row{cursor:pointer}.dt__row:hover td{background:var(--bg-hover)}.dt__row:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px;outline-offset:-2px}.dt__row--skel{cursor:default}.dt__row--skel:hover td{background:transparent}.dt__td--end{text-align:end}.dt__td--date,.dt__td--num{color:var(--ink-3);font-size:var(--text-sm);font-variant-numeric:tabular-nums}@container (width <= 720px){.dt thead{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.dt,.dt tbody,.dt tbody tr,.dt tbody td{display:block}.dt__row{padding:var(--sp-2) 0;border-block-end:1px solid var(--line-3)}.dt__row:last-child{border-block-end:0}.dt tbody td{display:flex;align-items:baseline;justify-content:space-between;gap:var(--sp-3);height:auto;border-block-end:0;padding:var(--sp-1) var(--sp-3);text-align:end}.dt tbody td:before{content:attr(data-label);flex-shrink:0;font-size:var(--text-xs);font-weight:var(--fw-medium);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-4);text-align:start}}@media(width<=720px){.dt thead{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.dt,.dt tbody,.dt tbody tr,.dt tbody td{display:block}.dt__row{padding:var(--sp-2) 0;border-block-end:1px solid var(--line-3)}.dt__row:last-child{border-block-end:0}.dt tbody td{display:flex;align-items:baseline;justify-content:space-between;gap:var(--sp-3);height:auto;border-block-end:0;padding:var(--sp-1) var(--sp-3);text-align:end}.dt tbody td:before{content:attr(data-label);flex-shrink:0;font-size:var(--text-xs);font-weight:var(--fw-medium);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-4);text-align:start}}\n"] }]
18175
18657
  }], propDecorators: { columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: true }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], ariaLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelKey", required: false }] }], sort: [{ type: i0.Input, args: [{ isSignal: true, alias: "sort", required: false }] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], rowActivated: [{ type: i0.Output, args: ["rowActivated"] }], emptyKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyKey", required: false }] }], emptyIconClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyIconClass", required: false }] }], cellTemplates: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => FlyCellDirective), { isSignal: true }] }] } });
18176
18658
 
18177
18659
  /**
@@ -18384,11 +18866,11 @@ class PageHeaderComponent {
18384
18866
  return '#';
18385
18867
  }
18386
18868
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: PageHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
18387
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: PageHeaderComponent, isStandalone: true, selector: "fly-page-header", inputs: { variant: "variant", title: "title", titleKey: "titleKey", subtitle: "subtitle", subtitleKey: "subtitleKey", titleAr: "titleAr", eyebrow: "eyebrow", eyebrowKey: "eyebrowKey", metricValue: "metricValue", metricLabel: "metricLabel", metricLabelKey: "metricLabelKey", breadcrumbs: "breadcrumbs" }, outputs: { crumbClick: "crumbClick" }, ngImport: i0, template: "<header class=\"ph\" [ngClass]=\"'ph--' + variant\">\r\n <!-- \u2500\u2500\u2500 Breadcrumbs \u2500\u2500\u2500 -->\r\n @if (breadcrumbs?.length) {\r\n <nav class=\"crumbs\" [attr.aria-label]=\"'ui.nav.breadcrumbs' | translate\">\r\n @for (bc of breadcrumbs; track $index; let i = $index, last = $last) {\r\n @if (i > 0) {\r\n <span class=\"crumb__sep\" aria-hidden=\"true\">/</span>\r\n }\r\n @if (last || bc.ariaCurrent) {\r\n <span class=\"crumb crumb--active\" aria-current=\"page\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </span>\r\n } @else if (bc.onClick) {\r\n <button type=\"button\" class=\"crumb crumb--link\" (click)=\"onCrumb(bc)\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </button>\r\n } @else if (bc.link) {\r\n <a class=\"crumb crumb--link\" (click)=\"onCrumb(bc); $event.preventDefault()\" [attr.href]=\"hrefFor(bc)\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </a>\r\n } @else if (bc.href) {\r\n <a class=\"crumb crumb--link\" [attr.href]=\"bc.href\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </a>\r\n } @else {\r\n <span class=\"crumb\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </span>\r\n }\r\n }\r\n </nav>\r\n }\r\n\r\n <!-- \u2500\u2500\u2500 Title row \u2500\u2500\u2500 -->\r\n <div class=\"ph__row\">\r\n <div class=\"ph__l\">\r\n @if (eyebrow || eyebrowKey) {\r\n <div class=\"ph__eyebrow\">\r\n @if (eyebrow) { <span class=\"mono\">{{ eyebrow }}</span> }\r\n @if (eyebrowKey) { <span>{{ eyebrowKey | translate }}</span> }\r\n </div>\r\n }\r\n <h1 class=\"ph__title\" [ngClass]=\"{ 'ph__title--detail': variant === 'detail' }\">\r\n {{ titleKey ? (titleKey | translate) : title }}\r\n </h1>\r\n @if (subtitle || subtitleKey) {\r\n <p class=\"ph__sub\">{{ subtitleKey ? (subtitleKey | translate) : subtitle }}</p>\r\n }\r\n @if (titleAr) {\r\n <p class=\"ph__title-ar\">{{ titleAr }}</p>\r\n }\r\n </div>\r\n\r\n <div class=\"ph__r\">\r\n <!-- Right-side metric (listing) -->\r\n @if (metricValue || metricLabel || metricLabelKey) {\r\n <div class=\"ph__metric\">\r\n @if (metricValue) { <div class=\"ph__metric-v mono\">{{ metricValue }}</div> }\r\n <div class=\"ph__metric-l\">{{ metricLabelKey ? (metricLabelKey | translate) : metricLabel }}</div>\r\n </div>\r\n }\r\n\r\n <!-- Status chip (detail) stacked over the action button row. Listing/circles/module\r\n project no status, so this collapses to just the right-aligned action row.\r\n NOTE: a single [page-header-actions] slot \u2014 duplicating it across @if branches\r\n silently drops projected content in the non-first branch (Angular binds projection\r\n to the first declared matching <ng-content>). -->\r\n <div class=\"ph__hd-actions\">\r\n <ng-content select=\"[page-header-status]\"></ng-content>\r\n <div class=\"ph__hd-btns\">\r\n <ng-content select=\"[page-header-actions]\"></ng-content>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Default slot: anything the consumer wants directly below the title row\r\n (e.g. a detail-page stat bar). -->\r\n <ng-content></ng-content>\r\n</header>\r\n", styles: ["@charset \"UTF-8\";:host{display:block;margin-block-end:var(--sp-6)}.ph{display:flex;flex-direction:column;gap:0;color:var(--ink);font-family:var(--font-sans, var(--font-family, system-ui, sans-serif))}.crumbs{display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-size:12px;margin-bottom:14px;color:var(--ink-3)}.crumb{font-size:12px;line-height:1.4;color:var(--ink-3);padding:2px 0;background:none;border:0;font-family:inherit;letter-spacing:-.005em;text-decoration:none}.crumb--link{cursor:pointer;transition:color var(--t-state)}.crumb--link:hover{color:var(--ink);text-decoration:underline;text-underline-offset:3px}.crumb--active{color:var(--ink);font-weight:var(--fw-medium);max-width:480px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.crumb__sep{color:var(--ink-4);font-size:11px;-webkit-user-select:none;user-select:none}.ph__row{display:flex;align-items:flex-end;justify-content:space-between;gap:32px;flex-wrap:wrap}.ph--detail .ph__row{align-items:flex-start}.ph__l{flex:1;min-width:0}.ph__r{display:flex;align-items:flex-end;gap:16px;flex-shrink:0}.ph--detail .ph__r{align-items:flex-end;flex-direction:column}.ph__eyebrow{display:inline-flex;align-items:center;gap:6px;font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-4);font-weight:var(--fw-medium);margin-bottom:4px}.ph__eyebrow .mono{font-size:10.5px;color:var(--ink-3);letter-spacing:.04em;font-family:var(--font-mono, ui-monospace, monospace)}.ph__title{margin:0 0 6px;font-size:32px;font-weight:var(--fw-semibold);letter-spacing:-.02em;line-height:1.1;color:var(--ink)}.ph__title--detail{font-size:28px;letter-spacing:-.02em;line-height:1.2;margin:0}.ph__sub{margin:0;font-size:13.5px;color:var(--ink-3)}.ph__title-ar{margin:6px 0 0;font-size:17px;color:var(--ink-3);font-family:Noto Kufi Arabic,sans-serif;letter-spacing:0;line-height:1.5;font-weight:var(--fw-medium)}.ph__metric{text-align:end}.ph__metric-v{font-family:var(--font-mono, ui-monospace, monospace);font-size:22px;font-weight:var(--fw-medium);letter-spacing:-.02em;color:var(--ink)}.ph__metric-l{font-size:11px;color:var(--ink-3);text-transform:uppercase;letter-spacing:.06em}.ph__hd-actions{display:flex;flex-direction:column;align-items:flex-end;gap:10px}.ph__hd-btns{display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end}.ph--listing,.ph--detail{margin-bottom:0}@media(max-width:980px){.ph__row{flex-direction:column;align-items:flex-start;gap:16px}.ph__r{align-items:flex-start;flex-direction:row;flex-wrap:wrap}.ph--detail .ph__r{flex-direction:row-reverse}.ph__title{font-size:26px}.ph__title--detail{font-size:24px}}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
18869
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: PageHeaderComponent, isStandalone: true, selector: "fly-page-header", inputs: { variant: "variant", title: "title", titleKey: "titleKey", subtitle: "subtitle", subtitleKey: "subtitleKey", titleAr: "titleAr", eyebrow: "eyebrow", eyebrowKey: "eyebrowKey", metricValue: "metricValue", metricLabel: "metricLabel", metricLabelKey: "metricLabelKey", breadcrumbs: "breadcrumbs" }, outputs: { crumbClick: "crumbClick" }, ngImport: i0, template: "<header class=\"ph\" [ngClass]=\"'ph--' + variant\">\r\n <!-- \u2500\u2500\u2500 Breadcrumbs \u2500\u2500\u2500 -->\r\n @if (breadcrumbs?.length) {\r\n <nav class=\"crumbs\" [attr.aria-label]=\"'ui.nav.breadcrumbs' | translate\">\r\n @for (bc of breadcrumbs; track $index; let i = $index, last = $last) {\r\n @if (i > 0) {\r\n <span class=\"crumb__sep\" aria-hidden=\"true\">/</span>\r\n }\r\n @if (last || bc.ariaCurrent) {\r\n <span class=\"crumb crumb--active\" aria-current=\"page\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </span>\r\n } @else if (bc.onClick) {\r\n <button type=\"button\" class=\"crumb crumb--link\" (click)=\"onCrumb(bc)\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </button>\r\n } @else if (bc.link) {\r\n <a class=\"crumb crumb--link\" (click)=\"onCrumb(bc); $event.preventDefault()\" [attr.href]=\"hrefFor(bc)\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </a>\r\n } @else if (bc.href) {\r\n <a class=\"crumb crumb--link\" [attr.href]=\"bc.href\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </a>\r\n } @else {\r\n <span class=\"crumb\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </span>\r\n }\r\n }\r\n </nav>\r\n }\r\n\r\n <!-- \u2500\u2500\u2500 Title row \u2500\u2500\u2500 -->\r\n <div class=\"ph__row\">\r\n <div class=\"ph__l\">\r\n @if (eyebrow || eyebrowKey) {\r\n <div class=\"ph__eyebrow\">\r\n @if (eyebrow) { <span class=\"mono\">{{ eyebrow }}</span> }\r\n @if (eyebrowKey) { <span>{{ eyebrowKey | translate }}</span> }\r\n </div>\r\n }\r\n <h1 class=\"ph__title\" [ngClass]=\"{ 'ph__title--detail': variant === 'detail' }\">\r\n {{ titleKey ? (titleKey | translate) : title }}\r\n </h1>\r\n @if (subtitle || subtitleKey) {\r\n <p class=\"ph__sub\">{{ subtitleKey ? (subtitleKey | translate) : subtitle }}</p>\r\n }\r\n @if (titleAr) {\r\n <p class=\"ph__title-ar\">{{ titleAr }}</p>\r\n }\r\n </div>\r\n\r\n <div class=\"ph__r\">\r\n <!-- Right-side metric (listing) -->\r\n @if (metricValue || metricLabel || metricLabelKey) {\r\n <div class=\"ph__metric\">\r\n @if (metricValue) { <div class=\"ph__metric-v mono\">{{ metricValue }}</div> }\r\n <div class=\"ph__metric-l\">{{ metricLabelKey ? (metricLabelKey | translate) : metricLabel }}</div>\r\n </div>\r\n }\r\n\r\n <!-- Status chip (detail) stacked over the action button row. Listing/circles/module\r\n project no status, so this collapses to just the right-aligned action row.\r\n NOTE: a single [page-header-actions] slot \u2014 duplicating it across @if branches\r\n silently drops projected content in the non-first branch (Angular binds projection\r\n to the first declared matching <ng-content>). -->\r\n <div class=\"ph__hd-actions\">\r\n <ng-content select=\"[page-header-status]\"></ng-content>\r\n <div class=\"ph__hd-btns\">\r\n <ng-content select=\"[page-header-actions]\"></ng-content>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Default slot: anything the consumer wants directly below the title row\r\n (e.g. a detail-page stat bar). -->\r\n <ng-content></ng-content>\r\n</header>\r\n", styles: ["@charset \"UTF-8\";:host{display:block;margin-block-end:var(--sp-6);container-type:inline-size}.ph{display:flex;flex-direction:column;gap:0;color:var(--ink);font-family:var(--font-sans, var(--font-family, system-ui, sans-serif))}.crumbs{display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-size:12px;margin-bottom:14px;color:var(--ink-3)}.crumb{font-size:12px;line-height:1.4;color:var(--ink-3);padding:2px 0;background:none;border:0;font-family:inherit;letter-spacing:-.005em;text-decoration:none}.crumb--link{cursor:pointer;transition:color var(--t-state)}.crumb--link:hover{color:var(--ink);text-decoration:underline;text-underline-offset:3px}.crumb--active{color:var(--ink);font-weight:var(--fw-medium);max-width:480px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.crumb__sep{color:var(--ink-4);font-size:11px;-webkit-user-select:none;user-select:none}.ph__row{display:flex;align-items:flex-end;justify-content:space-between;gap:32px;flex-wrap:wrap}.ph--detail .ph__row{align-items:flex-start}.ph__l{flex:1;min-width:0}.ph__r{display:flex;align-items:flex-end;gap:16px;flex-shrink:0}.ph--detail .ph__r{align-items:flex-end;flex-direction:column}.ph__eyebrow{display:inline-flex;align-items:center;gap:6px;font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-4);font-weight:var(--fw-medium);margin-bottom:4px}.ph__eyebrow .mono{font-size:10.5px;color:var(--ink-3);letter-spacing:.04em;font-family:var(--font-mono, ui-monospace, monospace)}.ph__title{margin:0 0 6px;font-size:32px;font-weight:var(--fw-semibold);letter-spacing:-.02em;line-height:1.1;color:var(--ink)}.ph__title--detail{font-size:28px;letter-spacing:-.02em;line-height:1.2;margin:0}.ph__sub{margin:0;font-size:13.5px;color:var(--ink-3)}.ph__title-ar{margin:6px 0 0;font-size:17px;color:var(--ink-3);font-family:Noto Kufi Arabic,sans-serif;letter-spacing:0;line-height:1.5;font-weight:var(--fw-medium)}.ph__metric{text-align:end}.ph__metric-v{font-family:var(--font-mono, ui-monospace, monospace);font-size:22px;font-weight:var(--fw-medium);letter-spacing:-.02em;color:var(--ink)}.ph__metric-l{font-size:11px;color:var(--ink-3);text-transform:uppercase;letter-spacing:.06em}.ph__hd-actions{display:flex;flex-direction:column;align-items:flex-end;gap:10px}.ph__hd-btns{display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end}.ph--listing,.ph--detail{margin-bottom:0}@container (max-width: 980px){.ph__row{flex-direction:column;align-items:flex-start;gap:16px}.ph__r{align-items:flex-start;flex-direction:row;flex-wrap:wrap}.ph--detail .ph__r{flex-direction:row-reverse}.ph__title{font-size:26px}.ph__title--detail{font-size:24px}}@media(max-width:980px){.ph__row{flex-direction:column;align-items:flex-start;gap:16px}.ph__r{align-items:flex-start;flex-direction:row;flex-wrap:wrap}.ph--detail .ph__r{flex-direction:row-reverse}.ph__title{font-size:26px}.ph__title--detail{font-size:24px}}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
18388
18870
  }
18389
18871
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: PageHeaderComponent, decorators: [{
18390
18872
  type: Component,
18391
- args: [{ selector: 'fly-page-header', standalone: true, imports: [NgClass, TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<header class=\"ph\" [ngClass]=\"'ph--' + variant\">\r\n <!-- \u2500\u2500\u2500 Breadcrumbs \u2500\u2500\u2500 -->\r\n @if (breadcrumbs?.length) {\r\n <nav class=\"crumbs\" [attr.aria-label]=\"'ui.nav.breadcrumbs' | translate\">\r\n @for (bc of breadcrumbs; track $index; let i = $index, last = $last) {\r\n @if (i > 0) {\r\n <span class=\"crumb__sep\" aria-hidden=\"true\">/</span>\r\n }\r\n @if (last || bc.ariaCurrent) {\r\n <span class=\"crumb crumb--active\" aria-current=\"page\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </span>\r\n } @else if (bc.onClick) {\r\n <button type=\"button\" class=\"crumb crumb--link\" (click)=\"onCrumb(bc)\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </button>\r\n } @else if (bc.link) {\r\n <a class=\"crumb crumb--link\" (click)=\"onCrumb(bc); $event.preventDefault()\" [attr.href]=\"hrefFor(bc)\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </a>\r\n } @else if (bc.href) {\r\n <a class=\"crumb crumb--link\" [attr.href]=\"bc.href\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </a>\r\n } @else {\r\n <span class=\"crumb\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </span>\r\n }\r\n }\r\n </nav>\r\n }\r\n\r\n <!-- \u2500\u2500\u2500 Title row \u2500\u2500\u2500 -->\r\n <div class=\"ph__row\">\r\n <div class=\"ph__l\">\r\n @if (eyebrow || eyebrowKey) {\r\n <div class=\"ph__eyebrow\">\r\n @if (eyebrow) { <span class=\"mono\">{{ eyebrow }}</span> }\r\n @if (eyebrowKey) { <span>{{ eyebrowKey | translate }}</span> }\r\n </div>\r\n }\r\n <h1 class=\"ph__title\" [ngClass]=\"{ 'ph__title--detail': variant === 'detail' }\">\r\n {{ titleKey ? (titleKey | translate) : title }}\r\n </h1>\r\n @if (subtitle || subtitleKey) {\r\n <p class=\"ph__sub\">{{ subtitleKey ? (subtitleKey | translate) : subtitle }}</p>\r\n }\r\n @if (titleAr) {\r\n <p class=\"ph__title-ar\">{{ titleAr }}</p>\r\n }\r\n </div>\r\n\r\n <div class=\"ph__r\">\r\n <!-- Right-side metric (listing) -->\r\n @if (metricValue || metricLabel || metricLabelKey) {\r\n <div class=\"ph__metric\">\r\n @if (metricValue) { <div class=\"ph__metric-v mono\">{{ metricValue }}</div> }\r\n <div class=\"ph__metric-l\">{{ metricLabelKey ? (metricLabelKey | translate) : metricLabel }}</div>\r\n </div>\r\n }\r\n\r\n <!-- Status chip (detail) stacked over the action button row. Listing/circles/module\r\n project no status, so this collapses to just the right-aligned action row.\r\n NOTE: a single [page-header-actions] slot \u2014 duplicating it across @if branches\r\n silently drops projected content in the non-first branch (Angular binds projection\r\n to the first declared matching <ng-content>). -->\r\n <div class=\"ph__hd-actions\">\r\n <ng-content select=\"[page-header-status]\"></ng-content>\r\n <div class=\"ph__hd-btns\">\r\n <ng-content select=\"[page-header-actions]\"></ng-content>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Default slot: anything the consumer wants directly below the title row\r\n (e.g. a detail-page stat bar). -->\r\n <ng-content></ng-content>\r\n</header>\r\n", styles: ["@charset \"UTF-8\";:host{display:block;margin-block-end:var(--sp-6)}.ph{display:flex;flex-direction:column;gap:0;color:var(--ink);font-family:var(--font-sans, var(--font-family, system-ui, sans-serif))}.crumbs{display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-size:12px;margin-bottom:14px;color:var(--ink-3)}.crumb{font-size:12px;line-height:1.4;color:var(--ink-3);padding:2px 0;background:none;border:0;font-family:inherit;letter-spacing:-.005em;text-decoration:none}.crumb--link{cursor:pointer;transition:color var(--t-state)}.crumb--link:hover{color:var(--ink);text-decoration:underline;text-underline-offset:3px}.crumb--active{color:var(--ink);font-weight:var(--fw-medium);max-width:480px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.crumb__sep{color:var(--ink-4);font-size:11px;-webkit-user-select:none;user-select:none}.ph__row{display:flex;align-items:flex-end;justify-content:space-between;gap:32px;flex-wrap:wrap}.ph--detail .ph__row{align-items:flex-start}.ph__l{flex:1;min-width:0}.ph__r{display:flex;align-items:flex-end;gap:16px;flex-shrink:0}.ph--detail .ph__r{align-items:flex-end;flex-direction:column}.ph__eyebrow{display:inline-flex;align-items:center;gap:6px;font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-4);font-weight:var(--fw-medium);margin-bottom:4px}.ph__eyebrow .mono{font-size:10.5px;color:var(--ink-3);letter-spacing:.04em;font-family:var(--font-mono, ui-monospace, monospace)}.ph__title{margin:0 0 6px;font-size:32px;font-weight:var(--fw-semibold);letter-spacing:-.02em;line-height:1.1;color:var(--ink)}.ph__title--detail{font-size:28px;letter-spacing:-.02em;line-height:1.2;margin:0}.ph__sub{margin:0;font-size:13.5px;color:var(--ink-3)}.ph__title-ar{margin:6px 0 0;font-size:17px;color:var(--ink-3);font-family:Noto Kufi Arabic,sans-serif;letter-spacing:0;line-height:1.5;font-weight:var(--fw-medium)}.ph__metric{text-align:end}.ph__metric-v{font-family:var(--font-mono, ui-monospace, monospace);font-size:22px;font-weight:var(--fw-medium);letter-spacing:-.02em;color:var(--ink)}.ph__metric-l{font-size:11px;color:var(--ink-3);text-transform:uppercase;letter-spacing:.06em}.ph__hd-actions{display:flex;flex-direction:column;align-items:flex-end;gap:10px}.ph__hd-btns{display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end}.ph--listing,.ph--detail{margin-bottom:0}@media(max-width:980px){.ph__row{flex-direction:column;align-items:flex-start;gap:16px}.ph__r{align-items:flex-start;flex-direction:row;flex-wrap:wrap}.ph--detail .ph__r{flex-direction:row-reverse}.ph__title{font-size:26px}.ph__title--detail{font-size:24px}}\n"] }]
18873
+ args: [{ selector: 'fly-page-header', standalone: true, imports: [NgClass, TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<header class=\"ph\" [ngClass]=\"'ph--' + variant\">\r\n <!-- \u2500\u2500\u2500 Breadcrumbs \u2500\u2500\u2500 -->\r\n @if (breadcrumbs?.length) {\r\n <nav class=\"crumbs\" [attr.aria-label]=\"'ui.nav.breadcrumbs' | translate\">\r\n @for (bc of breadcrumbs; track $index; let i = $index, last = $last) {\r\n @if (i > 0) {\r\n <span class=\"crumb__sep\" aria-hidden=\"true\">/</span>\r\n }\r\n @if (last || bc.ariaCurrent) {\r\n <span class=\"crumb crumb--active\" aria-current=\"page\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </span>\r\n } @else if (bc.onClick) {\r\n <button type=\"button\" class=\"crumb crumb--link\" (click)=\"onCrumb(bc)\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </button>\r\n } @else if (bc.link) {\r\n <a class=\"crumb crumb--link\" (click)=\"onCrumb(bc); $event.preventDefault()\" [attr.href]=\"hrefFor(bc)\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </a>\r\n } @else if (bc.href) {\r\n <a class=\"crumb crumb--link\" [attr.href]=\"bc.href\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </a>\r\n } @else {\r\n <span class=\"crumb\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </span>\r\n }\r\n }\r\n </nav>\r\n }\r\n\r\n <!-- \u2500\u2500\u2500 Title row \u2500\u2500\u2500 -->\r\n <div class=\"ph__row\">\r\n <div class=\"ph__l\">\r\n @if (eyebrow || eyebrowKey) {\r\n <div class=\"ph__eyebrow\">\r\n @if (eyebrow) { <span class=\"mono\">{{ eyebrow }}</span> }\r\n @if (eyebrowKey) { <span>{{ eyebrowKey | translate }}</span> }\r\n </div>\r\n }\r\n <h1 class=\"ph__title\" [ngClass]=\"{ 'ph__title--detail': variant === 'detail' }\">\r\n {{ titleKey ? (titleKey | translate) : title }}\r\n </h1>\r\n @if (subtitle || subtitleKey) {\r\n <p class=\"ph__sub\">{{ subtitleKey ? (subtitleKey | translate) : subtitle }}</p>\r\n }\r\n @if (titleAr) {\r\n <p class=\"ph__title-ar\">{{ titleAr }}</p>\r\n }\r\n </div>\r\n\r\n <div class=\"ph__r\">\r\n <!-- Right-side metric (listing) -->\r\n @if (metricValue || metricLabel || metricLabelKey) {\r\n <div class=\"ph__metric\">\r\n @if (metricValue) { <div class=\"ph__metric-v mono\">{{ metricValue }}</div> }\r\n <div class=\"ph__metric-l\">{{ metricLabelKey ? (metricLabelKey | translate) : metricLabel }}</div>\r\n </div>\r\n }\r\n\r\n <!-- Status chip (detail) stacked over the action button row. Listing/circles/module\r\n project no status, so this collapses to just the right-aligned action row.\r\n NOTE: a single [page-header-actions] slot \u2014 duplicating it across @if branches\r\n silently drops projected content in the non-first branch (Angular binds projection\r\n to the first declared matching <ng-content>). -->\r\n <div class=\"ph__hd-actions\">\r\n <ng-content select=\"[page-header-status]\"></ng-content>\r\n <div class=\"ph__hd-btns\">\r\n <ng-content select=\"[page-header-actions]\"></ng-content>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Default slot: anything the consumer wants directly below the title row\r\n (e.g. a detail-page stat bar). -->\r\n <ng-content></ng-content>\r\n</header>\r\n", styles: ["@charset \"UTF-8\";:host{display:block;margin-block-end:var(--sp-6);container-type:inline-size}.ph{display:flex;flex-direction:column;gap:0;color:var(--ink);font-family:var(--font-sans, var(--font-family, system-ui, sans-serif))}.crumbs{display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-size:12px;margin-bottom:14px;color:var(--ink-3)}.crumb{font-size:12px;line-height:1.4;color:var(--ink-3);padding:2px 0;background:none;border:0;font-family:inherit;letter-spacing:-.005em;text-decoration:none}.crumb--link{cursor:pointer;transition:color var(--t-state)}.crumb--link:hover{color:var(--ink);text-decoration:underline;text-underline-offset:3px}.crumb--active{color:var(--ink);font-weight:var(--fw-medium);max-width:480px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.crumb__sep{color:var(--ink-4);font-size:11px;-webkit-user-select:none;user-select:none}.ph__row{display:flex;align-items:flex-end;justify-content:space-between;gap:32px;flex-wrap:wrap}.ph--detail .ph__row{align-items:flex-start}.ph__l{flex:1;min-width:0}.ph__r{display:flex;align-items:flex-end;gap:16px;flex-shrink:0}.ph--detail .ph__r{align-items:flex-end;flex-direction:column}.ph__eyebrow{display:inline-flex;align-items:center;gap:6px;font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-4);font-weight:var(--fw-medium);margin-bottom:4px}.ph__eyebrow .mono{font-size:10.5px;color:var(--ink-3);letter-spacing:.04em;font-family:var(--font-mono, ui-monospace, monospace)}.ph__title{margin:0 0 6px;font-size:32px;font-weight:var(--fw-semibold);letter-spacing:-.02em;line-height:1.1;color:var(--ink)}.ph__title--detail{font-size:28px;letter-spacing:-.02em;line-height:1.2;margin:0}.ph__sub{margin:0;font-size:13.5px;color:var(--ink-3)}.ph__title-ar{margin:6px 0 0;font-size:17px;color:var(--ink-3);font-family:Noto Kufi Arabic,sans-serif;letter-spacing:0;line-height:1.5;font-weight:var(--fw-medium)}.ph__metric{text-align:end}.ph__metric-v{font-family:var(--font-mono, ui-monospace, monospace);font-size:22px;font-weight:var(--fw-medium);letter-spacing:-.02em;color:var(--ink)}.ph__metric-l{font-size:11px;color:var(--ink-3);text-transform:uppercase;letter-spacing:.06em}.ph__hd-actions{display:flex;flex-direction:column;align-items:flex-end;gap:10px}.ph__hd-btns{display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end}.ph--listing,.ph--detail{margin-bottom:0}@container (max-width: 980px){.ph__row{flex-direction:column;align-items:flex-start;gap:16px}.ph__r{align-items:flex-start;flex-direction:row;flex-wrap:wrap}.ph--detail .ph__r{flex-direction:row-reverse}.ph__title{font-size:26px}.ph__title--detail{font-size:24px}}@media(max-width:980px){.ph__row{flex-direction:column;align-items:flex-start;gap:16px}.ph__r{align-items:flex-start;flex-direction:row;flex-wrap:wrap}.ph--detail .ph__r{flex-direction:row-reverse}.ph__title{font-size:26px}.ph__title--detail{font-size:24px}}\n"] }]
18392
18874
  }], propDecorators: { variant: [{
18393
18875
  type: Input
18394
18876
  }], title: [{
@@ -18507,30 +18989,30 @@ class FlyDetailCardComponent {
18507
18989
  /** Drop the body padding — for a child that brings its own list/table chrome. */
18508
18990
  flush = input(false, ...(ngDevMode ? [{ debugName: "flush" }] : /* istanbul ignore next */ []));
18509
18991
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyDetailCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
18510
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyDetailCardComponent, isStandalone: true, selector: "fly-detail-card", inputs: { titleKey: { classPropertyName: "titleKey", publicName: "titleKey", isSignal: true, isRequired: false, transformFunction: null }, hasProjectedTitle: { classPropertyName: "hasProjectedTitle", publicName: "hasProjectedTitle", isSignal: true, isRequired: false, transformFunction: null }, flush: { classPropertyName: "flush", publicName: "flush", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
18511
- @if (titleKey() || hasProjectedTitle()) {
18512
- <fly-section-header [titleKey]="titleKey()">
18513
- <ng-content select="[card-title]" ngProjectAs="[section-title]" />
18514
- <ng-content select="[card-actions]" ngProjectAs="[section-actions]" />
18515
- </fly-section-header>
18516
- }
18517
- <div class="dc__body" [class.dc__body--flush]="flush()">
18518
- <ng-content />
18519
- </div>
18992
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyDetailCardComponent, isStandalone: true, selector: "fly-detail-card", inputs: { titleKey: { classPropertyName: "titleKey", publicName: "titleKey", isSignal: true, isRequired: false, transformFunction: null }, hasProjectedTitle: { classPropertyName: "hasProjectedTitle", publicName: "hasProjectedTitle", isSignal: true, isRequired: false, transformFunction: null }, flush: { classPropertyName: "flush", publicName: "flush", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
18993
+ @if (titleKey() || hasProjectedTitle()) {
18994
+ <fly-section-header [titleKey]="titleKey()">
18995
+ <ng-content select="[card-title]" ngProjectAs="[section-title]" />
18996
+ <ng-content select="[card-actions]" ngProjectAs="[section-actions]" />
18997
+ </fly-section-header>
18998
+ }
18999
+ <div class="dc__body" [class.dc__body--flush]="flush()">
19000
+ <ng-content />
19001
+ </div>
18520
19002
  `, isInline: true, styles: [":host{display:block;background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-lg);box-shadow:var(--shadow-card);overflow:hidden}.dc__body{padding:var(--sp-3) var(--sp-4) var(--sp-4)}.dc__body--flush{padding:0}\n"], dependencies: [{ kind: "component", type: FlySectionHeaderComponent, selector: "fly-section-header", inputs: ["titleKey"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
18521
19003
  }
18522
19004
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyDetailCardComponent, decorators: [{
18523
19005
  type: Component,
18524
- args: [{ selector: 'fly-detail-card', standalone: true, imports: [FlySectionHeaderComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
18525
- @if (titleKey() || hasProjectedTitle()) {
18526
- <fly-section-header [titleKey]="titleKey()">
18527
- <ng-content select="[card-title]" ngProjectAs="[section-title]" />
18528
- <ng-content select="[card-actions]" ngProjectAs="[section-actions]" />
18529
- </fly-section-header>
18530
- }
18531
- <div class="dc__body" [class.dc__body--flush]="flush()">
18532
- <ng-content />
18533
- </div>
19006
+ args: [{ selector: 'fly-detail-card', standalone: true, imports: [FlySectionHeaderComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
19007
+ @if (titleKey() || hasProjectedTitle()) {
19008
+ <fly-section-header [titleKey]="titleKey()">
19009
+ <ng-content select="[card-title]" ngProjectAs="[section-title]" />
19010
+ <ng-content select="[card-actions]" ngProjectAs="[section-actions]" />
19011
+ </fly-section-header>
19012
+ }
19013
+ <div class="dc__body" [class.dc__body--flush]="flush()">
19014
+ <ng-content />
19015
+ </div>
18534
19016
  `, styles: [":host{display:block;background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-lg);box-shadow:var(--shadow-card);overflow:hidden}.dc__body{padding:var(--sp-3) var(--sp-4) var(--sp-4)}.dc__body--flush{padding:0}\n"] }]
18535
19017
  }], propDecorators: { titleKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleKey", required: false }] }], hasProjectedTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasProjectedTitle", required: false }] }], flush: [{ type: i0.Input, args: [{ isSignal: true, alias: "flush", required: false }] }] } });
18536
19018
 
@@ -18658,109 +19140,109 @@ class FlyDetailShellComponent {
18658
19140
  buttons?.[next]?.focus();
18659
19141
  }
18660
19142
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyDetailShellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
18661
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyDetailShellComponent, isStandalone: true, selector: "fly-detail-shell", inputs: { sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, sectionsLabelKey: { classPropertyName: "sectionsLabelKey", publicName: "sectionsLabelKey", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selectedChange" }, viewQueries: [{ propertyName: "rail", first: true, predicate: ["rail"], descendants: true, isSignal: true }], ngImport: i0, template: `
18662
- <div class="ds__layout">
18663
- <aside class="ds__aside">
18664
- <div class="ds__pinned">
18665
- <ng-content select="[detail-aside]" />
18666
- </div>
18667
-
18668
- @if (sections().length > 0) {
18669
- <div
18670
- #rail
18671
- class="ds__rail"
18672
- role="tablist"
18673
- tabindex="-1"
18674
- [attr.aria-orientation]="'vertical'"
18675
- [attr.aria-label]="sectionsLabelKey() | translate"
18676
- (keydown)="onKey($event)"
18677
- >
18678
- @for (s of sections(); track s.id) {
18679
- <button
18680
- type="button"
18681
- class="ds__tab"
18682
- role="tab"
18683
- [id]="tabId(s.id)"
18684
- [class.ds__tab--active]="activeId() === s.id"
18685
- [attr.aria-selected]="activeId() === s.id"
18686
- [attr.aria-controls]="panelId()"
18687
- [attr.tabindex]="activeId() === s.id ? 0 : -1"
18688
- [attr.title]="s.labelKey | translate"
18689
- (click)="select(s.id)"
18690
- >
18691
- @if (s.icon) {
18692
- <span class="pi {{ s.icon }} ds__tab-ico" aria-hidden="true"></span>
18693
- }
18694
- <span class="ds__tab-label">{{ s.labelKey | translate }}</span>
18695
- </button>
18696
- }
18697
- </div>
18698
- }
18699
- </aside>
18700
-
18701
- <div
18702
- class="ds__panel"
18703
- [id]="panelId()"
18704
- [attr.role]="sections().length ? 'tabpanel' : null"
18705
- [attr.aria-labelledby]="activeId() ? tabId(activeId()!) : null"
18706
- >
18707
- <ng-content />
18708
- </div>
18709
- </div>
18710
- `, isInline: true, styles: [":host{display:block}.ds__layout{display:grid;grid-template-columns:var(--fly-detail-aside, 300px) minmax(0,1fr);gap:var(--sp-4);align-items:start}.ds__aside{display:flex;flex-direction:column;gap:var(--sp-4);min-width:0}.ds__aside .ds__rail{order:-1}.ds__pinned{display:flex;flex-direction:column;gap:var(--sp-4);min-width:0}.ds__panel{display:flex;flex-direction:column;gap:var(--sp-4);min-width:0;align-self:stretch}.ds__panel>:only-child{flex:1}.ds__rail{display:flex;flex-direction:column;gap:var(--sp-1);padding:var(--sp-2);background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-lg);box-shadow:var(--shadow-card)}.ds__tab{display:flex;align-items:center;gap:var(--sp-3);width:100%;padding:var(--sp-2);border:0;background:transparent;border-radius:var(--r-md);color:var(--ink-2);cursor:pointer;font-family:inherit;font-size:var(--text-sm);text-align:start;white-space:nowrap;overflow:hidden;transition:background var(--t-state),color var(--t-state)}.ds__tab:hover{background:var(--bg-hover);color:var(--ink)}.ds__tab:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.ds__tab--active{background:var(--accent-fill);color:var(--on-accent-fill);font-weight:var(--fw-semibold)}.ds__tab-ico{font-size:var(--text-md);flex-shrink:0;width:20px;text-align:center}.ds__tab-label{min-width:0;overflow:hidden;text-overflow:ellipsis}@media(max-width:980px){.ds__layout{grid-template-columns:minmax(0,1fr)}.ds__rail{flex-direction:row;flex-wrap:wrap}.ds__tab{width:auto}}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
19143
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyDetailShellComponent, isStandalone: true, selector: "fly-detail-shell", inputs: { sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, sectionsLabelKey: { classPropertyName: "sectionsLabelKey", publicName: "sectionsLabelKey", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selectedChange" }, viewQueries: [{ propertyName: "rail", first: true, predicate: ["rail"], descendants: true, isSignal: true }], ngImport: i0, template: `
19144
+ <div class="ds__layout">
19145
+ <aside class="ds__aside">
19146
+ <div class="ds__pinned">
19147
+ <ng-content select="[detail-aside]" />
19148
+ </div>
19149
+
19150
+ @if (sections().length > 0) {
19151
+ <div
19152
+ #rail
19153
+ class="ds__rail"
19154
+ role="tablist"
19155
+ tabindex="-1"
19156
+ [attr.aria-orientation]="'vertical'"
19157
+ [attr.aria-label]="sectionsLabelKey() | translate"
19158
+ (keydown)="onKey($event)"
19159
+ >
19160
+ @for (s of sections(); track s.id) {
19161
+ <button
19162
+ type="button"
19163
+ class="ds__tab"
19164
+ role="tab"
19165
+ [id]="tabId(s.id)"
19166
+ [class.ds__tab--active]="activeId() === s.id"
19167
+ [attr.aria-selected]="activeId() === s.id"
19168
+ [attr.aria-controls]="panelId()"
19169
+ [attr.tabindex]="activeId() === s.id ? 0 : -1"
19170
+ [attr.title]="s.labelKey | translate"
19171
+ (click)="select(s.id)"
19172
+ >
19173
+ @if (s.icon) {
19174
+ <span class="pi {{ s.icon }} ds__tab-ico" aria-hidden="true"></span>
19175
+ }
19176
+ <span class="ds__tab-label">{{ s.labelKey | translate }}</span>
19177
+ </button>
19178
+ }
19179
+ </div>
19180
+ }
19181
+ </aside>
19182
+
19183
+ <div
19184
+ class="ds__panel"
19185
+ [id]="panelId()"
19186
+ [attr.role]="sections().length ? 'tabpanel' : null"
19187
+ [attr.aria-labelledby]="activeId() ? tabId(activeId()!) : null"
19188
+ >
19189
+ <ng-content />
19190
+ </div>
19191
+ </div>
19192
+ `, isInline: true, styles: [":host{display:block;container-type:inline-size}.ds__layout{display:grid;grid-template-columns:var(--fly-detail-aside, 300px) minmax(0,1fr);gap:var(--sp-4);align-items:start}.ds__aside{display:flex;flex-direction:column;gap:var(--sp-4);min-width:0}.ds__aside .ds__rail{order:-1}.ds__pinned{display:flex;flex-direction:column;gap:var(--sp-4);min-width:0}.ds__panel{display:flex;flex-direction:column;gap:var(--sp-4);min-width:0;align-self:stretch}.ds__panel>:only-child{flex:1}.ds__rail{display:flex;flex-direction:column;gap:var(--sp-1);padding:var(--sp-2);background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-lg);box-shadow:var(--shadow-card)}.ds__tab{display:flex;align-items:center;gap:var(--sp-3);width:100%;padding:var(--sp-2);border:0;background:transparent;border-radius:var(--r-md);color:var(--ink-2);cursor:pointer;font-family:inherit;font-size:var(--text-sm);text-align:start;white-space:nowrap;overflow:hidden;transition:background var(--t-state),color var(--t-state)}.ds__tab:hover{background:var(--bg-hover);color:var(--ink)}.ds__tab:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.ds__tab--active{background:var(--accent-fill);color:var(--on-accent-fill);font-weight:var(--fw-semibold)}.ds__tab-ico{font-size:var(--text-md);flex-shrink:0;width:20px;text-align:center}.ds__tab-label{min-width:0;overflow:hidden;text-overflow:ellipsis}@container (max-width: 980px){.ds__layout{grid-template-columns:minmax(0,1fr)}.ds__rail{flex-direction:row;flex-wrap:wrap}.ds__tab{width:auto}}@media(max-width:980px){.ds__layout{grid-template-columns:minmax(0,1fr)}.ds__rail{flex-direction:row;flex-wrap:wrap}.ds__tab{width:auto}}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
18711
19193
  }
18712
19194
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyDetailShellComponent, decorators: [{
18713
19195
  type: Component,
18714
- args: [{ selector: 'fly-detail-shell', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: `
18715
- <div class="ds__layout">
18716
- <aside class="ds__aside">
18717
- <div class="ds__pinned">
18718
- <ng-content select="[detail-aside]" />
18719
- </div>
18720
-
18721
- @if (sections().length > 0) {
18722
- <div
18723
- #rail
18724
- class="ds__rail"
18725
- role="tablist"
18726
- tabindex="-1"
18727
- [attr.aria-orientation]="'vertical'"
18728
- [attr.aria-label]="sectionsLabelKey() | translate"
18729
- (keydown)="onKey($event)"
18730
- >
18731
- @for (s of sections(); track s.id) {
18732
- <button
18733
- type="button"
18734
- class="ds__tab"
18735
- role="tab"
18736
- [id]="tabId(s.id)"
18737
- [class.ds__tab--active]="activeId() === s.id"
18738
- [attr.aria-selected]="activeId() === s.id"
18739
- [attr.aria-controls]="panelId()"
18740
- [attr.tabindex]="activeId() === s.id ? 0 : -1"
18741
- [attr.title]="s.labelKey | translate"
18742
- (click)="select(s.id)"
18743
- >
18744
- @if (s.icon) {
18745
- <span class="pi {{ s.icon }} ds__tab-ico" aria-hidden="true"></span>
18746
- }
18747
- <span class="ds__tab-label">{{ s.labelKey | translate }}</span>
18748
- </button>
18749
- }
18750
- </div>
18751
- }
18752
- </aside>
18753
-
18754
- <div
18755
- class="ds__panel"
18756
- [id]="panelId()"
18757
- [attr.role]="sections().length ? 'tabpanel' : null"
18758
- [attr.aria-labelledby]="activeId() ? tabId(activeId()!) : null"
18759
- >
18760
- <ng-content />
18761
- </div>
18762
- </div>
18763
- `, styles: [":host{display:block}.ds__layout{display:grid;grid-template-columns:var(--fly-detail-aside, 300px) minmax(0,1fr);gap:var(--sp-4);align-items:start}.ds__aside{display:flex;flex-direction:column;gap:var(--sp-4);min-width:0}.ds__aside .ds__rail{order:-1}.ds__pinned{display:flex;flex-direction:column;gap:var(--sp-4);min-width:0}.ds__panel{display:flex;flex-direction:column;gap:var(--sp-4);min-width:0;align-self:stretch}.ds__panel>:only-child{flex:1}.ds__rail{display:flex;flex-direction:column;gap:var(--sp-1);padding:var(--sp-2);background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-lg);box-shadow:var(--shadow-card)}.ds__tab{display:flex;align-items:center;gap:var(--sp-3);width:100%;padding:var(--sp-2);border:0;background:transparent;border-radius:var(--r-md);color:var(--ink-2);cursor:pointer;font-family:inherit;font-size:var(--text-sm);text-align:start;white-space:nowrap;overflow:hidden;transition:background var(--t-state),color var(--t-state)}.ds__tab:hover{background:var(--bg-hover);color:var(--ink)}.ds__tab:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.ds__tab--active{background:var(--accent-fill);color:var(--on-accent-fill);font-weight:var(--fw-semibold)}.ds__tab-ico{font-size:var(--text-md);flex-shrink:0;width:20px;text-align:center}.ds__tab-label{min-width:0;overflow:hidden;text-overflow:ellipsis}@media(max-width:980px){.ds__layout{grid-template-columns:minmax(0,1fr)}.ds__rail{flex-direction:row;flex-wrap:wrap}.ds__tab{width:auto}}\n"] }]
19196
+ args: [{ selector: 'fly-detail-shell', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: `
19197
+ <div class="ds__layout">
19198
+ <aside class="ds__aside">
19199
+ <div class="ds__pinned">
19200
+ <ng-content select="[detail-aside]" />
19201
+ </div>
19202
+
19203
+ @if (sections().length > 0) {
19204
+ <div
19205
+ #rail
19206
+ class="ds__rail"
19207
+ role="tablist"
19208
+ tabindex="-1"
19209
+ [attr.aria-orientation]="'vertical'"
19210
+ [attr.aria-label]="sectionsLabelKey() | translate"
19211
+ (keydown)="onKey($event)"
19212
+ >
19213
+ @for (s of sections(); track s.id) {
19214
+ <button
19215
+ type="button"
19216
+ class="ds__tab"
19217
+ role="tab"
19218
+ [id]="tabId(s.id)"
19219
+ [class.ds__tab--active]="activeId() === s.id"
19220
+ [attr.aria-selected]="activeId() === s.id"
19221
+ [attr.aria-controls]="panelId()"
19222
+ [attr.tabindex]="activeId() === s.id ? 0 : -1"
19223
+ [attr.title]="s.labelKey | translate"
19224
+ (click)="select(s.id)"
19225
+ >
19226
+ @if (s.icon) {
19227
+ <span class="pi {{ s.icon }} ds__tab-ico" aria-hidden="true"></span>
19228
+ }
19229
+ <span class="ds__tab-label">{{ s.labelKey | translate }}</span>
19230
+ </button>
19231
+ }
19232
+ </div>
19233
+ }
19234
+ </aside>
19235
+
19236
+ <div
19237
+ class="ds__panel"
19238
+ [id]="panelId()"
19239
+ [attr.role]="sections().length ? 'tabpanel' : null"
19240
+ [attr.aria-labelledby]="activeId() ? tabId(activeId()!) : null"
19241
+ >
19242
+ <ng-content />
19243
+ </div>
19244
+ </div>
19245
+ `, styles: [":host{display:block;container-type:inline-size}.ds__layout{display:grid;grid-template-columns:var(--fly-detail-aside, 300px) minmax(0,1fr);gap:var(--sp-4);align-items:start}.ds__aside{display:flex;flex-direction:column;gap:var(--sp-4);min-width:0}.ds__aside .ds__rail{order:-1}.ds__pinned{display:flex;flex-direction:column;gap:var(--sp-4);min-width:0}.ds__panel{display:flex;flex-direction:column;gap:var(--sp-4);min-width:0;align-self:stretch}.ds__panel>:only-child{flex:1}.ds__rail{display:flex;flex-direction:column;gap:var(--sp-1);padding:var(--sp-2);background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-lg);box-shadow:var(--shadow-card)}.ds__tab{display:flex;align-items:center;gap:var(--sp-3);width:100%;padding:var(--sp-2);border:0;background:transparent;border-radius:var(--r-md);color:var(--ink-2);cursor:pointer;font-family:inherit;font-size:var(--text-sm);text-align:start;white-space:nowrap;overflow:hidden;transition:background var(--t-state),color var(--t-state)}.ds__tab:hover{background:var(--bg-hover);color:var(--ink)}.ds__tab:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.ds__tab--active{background:var(--accent-fill);color:var(--on-accent-fill);font-weight:var(--fw-semibold)}.ds__tab-ico{font-size:var(--text-md);flex-shrink:0;width:20px;text-align:center}.ds__tab-label{min-width:0;overflow:hidden;text-overflow:ellipsis}@container (max-width: 980px){.ds__layout{grid-template-columns:minmax(0,1fr)}.ds__rail{flex-direction:row;flex-wrap:wrap}.ds__tab{width:auto}}@media(max-width:980px){.ds__layout{grid-template-columns:minmax(0,1fr)}.ds__rail{flex-direction:row;flex-wrap:wrap}.ds__tab{width:auto}}\n"] }]
18764
19246
  }], propDecorators: { rail: [{ type: i0.ViewChild, args: ['rail', { isSignal: true }] }], sections: [{ type: i0.Input, args: [{ isSignal: true, alias: "sections", required: false }] }], selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }, { type: i0.Output, args: ["selectedChange"] }], sectionsLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "sectionsLabelKey", required: false }] }] } });
18765
19247
 
18766
19248
  /** Every module across every section, in visual order. */
@@ -18996,6 +19478,141 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
18996
19478
  args: [{ selector: 'fly-app-topbar', standalone: true, imports: [TranslatePipe, FlyClickOutsideDirective, NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, host: { '(document:keydown.escape)': 'onEscape()' }, template: "<header class=\"fly-topbar\">\r\n <div class=\"fly-topbar__lead\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-topbar__brand\"\r\n (click)=\"brandSelected.emit()\"\r\n [attr.aria-label]=\"brandAriaLabel()\"\r\n >\r\n <span class=\"fly-topbar__brand-mark\"><ng-content select=\"[app-topbar-brand]\" /></span>\r\n @if (brandLabelKey(); as key) {\r\n <span class=\"fly-topbar__brand-name\">{{ key | translate }}</span>\r\n }\r\n </button>\r\n\r\n @if (activeModule()) {\r\n <span class=\"fly-topbar__sep\" aria-hidden=\"true\">/</span>\r\n }\r\n\r\n <div class=\"fly-topbar__anchor\" (flyClickOutside)=\"close()\" [flyClickOutsideEnabled]=\"open()\">\r\n <button\r\n #trigger\r\n type=\"button\"\r\n class=\"fly-topbar__module\"\r\n [class.fly-topbar__module--on]=\"open()\"\r\n (click)=\"toggle()\"\r\n (keydown)=\"onTriggerKeydown($event)\"\r\n aria-haspopup=\"menu\"\r\n [attr.aria-expanded]=\"open()\"\r\n [attr.aria-label]=\"switchAriaLabel()\"\r\n >\r\n @if (activeModule(); as active) {\r\n <span class=\"fly-topbar__module-icon\">\r\n @if (iconFor(active.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (active.iconClass) {\r\n <i [class]=\"active.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n <span class=\"fly-topbar__module-label\">{{ active.labelKey | translate }}</span>\r\n } @else {\r\n <span class=\"fly-topbar__module-label fly-topbar__module-label--empty\">\r\n {{ 'ui.nav.selectModule' | translate }}\r\n </span>\r\n }\r\n <svg class=\"fly-topbar__chevron\" width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\">\r\n <polyline points=\"6 9 12 15 18 9\" />\r\n </svg>\r\n </button>\r\n\r\n @if (open()) {\r\n <div class=\"fly-topbar__pop\" role=\"menu\" [attr.aria-label]=\"switchAriaLabel()\">\r\n <div class=\"fly-topbar__cols\">\r\n @for (section of sections(); track $index) {\r\n <div class=\"fly-topbar__col\">\r\n @if (section.titleKey; as titleKey) {\r\n <div class=\"fly-topbar__col-title\">{{ titleKey | translate }}</div>\r\n }\r\n @for (mod of section.modules; track mod.key) {\r\n <button\r\n #row\r\n type=\"button\"\r\n class=\"fly-topbar__item\"\r\n [class.fly-topbar__item--on]=\"mod.key === activeKey()\"\r\n role=\"menuitem\"\r\n [disabled]=\"mod.disabled ?? false\"\r\n [attr.tabindex]=\"indexOf(mod) === focusIndex() ? 0 : -1\"\r\n [attr.aria-current]=\"mod.key === activeKey() ? 'page' : null\"\r\n (click)=\"select(mod)\"\r\n (keydown)=\"onRowKeydown($event)\"\r\n >\r\n <span class=\"fly-topbar__item-icon\">\r\n @if (iconFor(mod.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (mod.iconClass) {\r\n <i [class]=\"mod.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n <span class=\"fly-topbar__item-main\">\r\n <span class=\"fly-topbar__item-label\">{{ mod.labelKey | translate }}</span>\r\n @if (mod.descKey; as descKey) {\r\n <span class=\"fly-topbar__item-desc\">{{ descKey | translate }}</span>\r\n }\r\n </span>\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n @if (footerLabel()) {\r\n <div class=\"fly-topbar__foot\">{{ footerLabel() }}</div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"fly-topbar__actions\"><ng-content select=\"[app-topbar-actions]\" /></div>\r\n</header>\r\n", styles: [":host{display:block;position:relative;z-index:var(--z-sticky)}.fly-topbar{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-3);padding:var(--sp-2) var(--sp-3);border-block-end:1px solid var(--line);background:var(--bg-3)}.fly-topbar__lead{display:flex;align-items:center;gap:var(--sp-2);min-inline-size:0}.fly-topbar__brand,.fly-topbar__module{display:inline-flex;align-items:center;gap:var(--sp-2);appearance:none;border:0;background:transparent;cursor:pointer;font:inherit;color:var(--ink);padding:var(--sp-1) var(--sp-2);border-radius:var(--r-md);transition:background var(--t-state)}.fly-topbar__brand:hover,.fly-topbar__module:hover{background:var(--bg-hover)}.fly-topbar__brand:focus-visible,.fly-topbar__module:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fly-topbar__brand-name{font-weight:var(--fw-semibold);white-space:nowrap}.fly-topbar__brand-mark{display:inline-flex;align-items:center;max-block-size:var(--icon-lg)}.fly-topbar__sep{color:var(--ink-4);-webkit-user-select:none;user-select:none}.fly-topbar__module{min-inline-size:0}.fly-topbar__module--on{background:var(--bg-hover)}.fly-topbar__module-label{font-weight:var(--fw-medium);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-topbar__module-label--empty{color:var(--ink-3)}.fly-topbar__module-icon,.fly-topbar__item-icon{display:inline-flex;align-items:center;justify-content:center;inline-size:var(--icon-md);block-size:var(--icon-md);flex:0 0 auto;color:var(--accent)}.fly-topbar__chevron{flex:0 0 auto;color:var(--ink-3);transition:transform var(--t-state)}.fly-topbar__module--on .fly-topbar__chevron{transform:rotate(180deg)}.fly-topbar__anchor{position:relative}.fly-topbar__pop{position:absolute;inset-block-start:calc(100% + var(--sp-1));inset-inline-start:0;z-index:var(--z-overlay);min-inline-size:260px;max-inline-size:min(680px,92vw);padding:var(--sp-3);border:1px solid var(--line);border-radius:var(--r-lg);background:var(--bg-2);box-shadow:var(--shadow-panel)}.fly-topbar__cols{display:flex;gap:var(--sp-4);align-items:flex-start}@media(width<=720px){.fly-topbar__cols{flex-direction:column;gap:var(--sp-3)}}.fly-topbar__col{display:flex;flex-direction:column;gap:2px;min-inline-size:220px;flex:1 1 0}.fly-topbar__col-title{font-size:var(--text-2xs);font-weight:var(--fw-semibold);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-3);padding:var(--sp-2) var(--sp-2) var(--sp-1)}.fly-topbar__item{display:flex;align-items:flex-start;gap:var(--sp-2);inline-size:100%;text-align:start;appearance:none;border:0;background:transparent;cursor:pointer;font:inherit;color:var(--ink);padding:var(--sp-2);border-radius:var(--r-md);transition:background var(--t-state)}.fly-topbar__item:hover:not(:disabled){background:var(--bg-hover)}.fly-topbar__item:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-2px}.fly-topbar__item:disabled{opacity:.5;cursor:default}.fly-topbar__item--on{background:var(--accent-soft);color:var(--on-accent-soft);box-shadow:inset 2px 0 0 0 var(--accent)}.fly-topbar__item-main{display:flex;flex-direction:column;gap:1px;min-inline-size:0}.fly-topbar__item-label{font-weight:var(--fw-medium);line-height:1.3}.fly-topbar__item-desc{font-size:var(--text-xs);color:var(--ink-3);line-height:1.35}.fly-topbar__foot{margin-block-start:var(--sp-3);padding-block-start:var(--sp-2);border-block-start:1px solid var(--line-3);font-size:var(--text-2xs);color:var(--ink-4)}.fly-topbar__actions{display:flex;align-items:center;gap:var(--sp-2);flex:0 0 auto}@media(forced-colors:active){.fly-topbar__item--on{border-inline-start:2px solid CanvasText}}@media(prefers-reduced-motion:reduce){.fly-topbar__brand,.fly-topbar__module,.fly-topbar__item,.fly-topbar__chevron{transition:none}}\n"] }]
18997
19479
  }], ctorParameters: () => [], propDecorators: { brandLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "brandLabelKey", required: false }] }], sections: [{ type: i0.Input, args: [{ isSignal: true, alias: "sections", required: false }] }], activeKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeKey", required: false }] }], footerLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "footerLabel", required: false }] }], moduleSelected: [{ type: i0.Output, args: ["moduleSelected"] }], brandSelected: [{ type: i0.Output, args: ["brandSelected"] }], icons: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => FlyModuleIconDirective), { isSignal: true }] }], rows: [{ type: i0.ViewChildren, args: ['row', { isSignal: true }] }], trigger: [{ type: i0.ViewChildren, args: ['trigger', { isSignal: true }] }] } });
18998
19480
 
19481
+ /**
19482
+ * The section's card shape, applying the default when it declares none: the FIRST
19483
+ * section is the app's front door and gets tall feature cards; everything after it is
19484
+ * secondary and gets compact rows.
19485
+ *
19486
+ * Positional rather than required because the common case — one prominent section plus
19487
+ * a "supporting" tail — should need no configuration at all, and an app that wants
19488
+ * something else says so explicitly.
19489
+ */
19490
+ function sectionLayout(section, index) {
19491
+ return section.layout ?? (index === 0 ? 'feature' : 'compact');
19492
+ }
19493
+ /**
19494
+ * Which sections start expanded: every non-collapsible one, plus none of the
19495
+ * collapsible ones.
19496
+ *
19497
+ * Returned as a `Set` of indices rather than mutating the sections, so the input array
19498
+ * stays the app's immutable declaration and re-rendering with new data cannot resurrect
19499
+ * a stale open/closed state.
19500
+ */
19501
+ function initialExpanded(sections) {
19502
+ const open = new Set();
19503
+ sections.forEach((s, i) => {
19504
+ if (!s.collapsible)
19505
+ open.add(i);
19506
+ });
19507
+ return open;
19508
+ }
19509
+ /**
19510
+ * The hint key for a collapsible section in its current state, or null when it has none.
19511
+ *
19512
+ * `collapsedHintKey` falls back to `hintKey` so an app that wants one message in both
19513
+ * states declares one key. A non-collapsible section never shows a hint — the hint
19514
+ * exists to explain what is hidden.
19515
+ */
19516
+ function sectionHintKey(section, expanded) {
19517
+ if (!section.collapsible)
19518
+ return null;
19519
+ return (expanded ? section.hintKey : (section.collapsedHintKey ?? section.hintKey)) ?? null;
19520
+ }
19521
+
19522
+ /**
19523
+ * `<fly-app-home>` — a business app's landing page: brand strip, hero, and one card
19524
+ * per module.
19525
+ *
19526
+ * The sibling of {@link FlyAppTopbarComponent}, and deliberately fed by the SAME
19527
+ * `FlyAppModuleSection[]`. An app declares its modules once; the switcher is how you
19528
+ * move between them once you are inside, this is the front door you arrive at. Two
19529
+ * registries would drift, and the drift would show as a module reachable from one and
19530
+ * not the other.
19531
+ *
19532
+ * ```html
19533
+ * <fly-app-home
19534
+ * brandLabelKey="common.label.thoughts"
19535
+ * titleKey="thoughts.home.title"
19536
+ * subtitleKey="thoughts.home.subtitle"
19537
+ * [sections]="navSections"
19538
+ * (moduleSelected)="open($event)">
19539
+ * <span app-home-brand><fly-thoughts-logo /></span>
19540
+ * <div app-home-actions><!-- locale / theme / profile --></div>
19541
+ * <ng-template flyModuleIcon="ideas"><svg …></svg></ng-template>
19542
+ * </fly-app-home>
19543
+ * ```
19544
+ *
19545
+ * Generalized from the Circles landing, whose layout was sound but whose content was
19546
+ * hardcoded — one inline `<svg>` and one `@if` per module, plus a bespoke card for each
19547
+ * non-module destination. Here every card is data, and a destination that is not really
19548
+ * a module (a gallery, a cross-cutting control room) is just another {@link FlyAppModule}
19549
+ * with a projected icon. That was the test of whether this abstraction was real.
19550
+ *
19551
+ * Icons reuse `flyModuleIcon` — the SAME directive the topbar takes — so an app that
19552
+ * renders both declares its icon templates once per surface, in the same syntax.
19553
+ *
19554
+ * a11y: cards are real `<button>`s in a labelled group; a collapsible section is a
19555
+ * disclosure (`aria-expanded` + `aria-controls`), and the region carries the section
19556
+ * heading. Layout is logical-property only, so it mirrors under `dir="rtl"` on its own —
19557
+ * the one exception is the chevron/arrow glyphs, which are directional by meaning and
19558
+ * flip explicitly.
19559
+ */
19560
+ class FlyAppHomeComponent {
19561
+ /** i18n key for the brand name beside the mark. Omit to render the mark alone. */
19562
+ brandLabelKey = input(...(ngDevMode ? [undefined, { debugName: "brandLabelKey" }] : /* istanbul ignore next */ []));
19563
+ /** i18n key for the hero headline. */
19564
+ titleKey = input(...(ngDevMode ? [undefined, { debugName: "titleKey" }] : /* istanbul ignore next */ []));
19565
+ /** i18n key for the hero sub-line. */
19566
+ subtitleKey = input(...(ngDevMode ? [undefined, { debugName: "subtitleKey" }] : /* istanbul ignore next */ []));
19567
+ /** The app's modules, grouped. Same array the topbar takes. */
19568
+ sections = input([], ...(ngDevMode ? [{ debugName: "sections" }] : /* istanbul ignore next */ []));
19569
+ /** i18n key for the card group's `aria-label`. */
19570
+ modulesLabelKey = input('ui.nav.modules', ...(ngDevMode ? [{ debugName: "modulesLabelKey" }] : /* istanbul ignore next */ []));
19571
+ /** Emits the selected module's `key`. Disabled modules never emit. */
19572
+ moduleSelected = output();
19573
+ /** Emits when the brand is activated. */
19574
+ brandSelected = output();
19575
+ icons = contentChildren(FlyModuleIconDirective, ...(ngDevMode ? [{ debugName: "icons" }] : /* istanbul ignore next */ []));
19576
+ /**
19577
+ * Expanded section indices. Seeded from the inputs and then owned by the user —
19578
+ * `linkedSignal` semantics on purpose: re-declaring `sections` (a locale switch
19579
+ * re-evaluating labels, say) must not slam a section the user opened shut again.
19580
+ */
19581
+ userToggled = signal(null, ...(ngDevMode ? [{ debugName: "userToggled" }] : /* istanbul ignore next */ []));
19582
+ expandedSet = computed(() => this.userToggled() ?? initialExpanded(this.sections()), ...(ngDevMode ? [{ debugName: "expandedSet" }] : /* istanbul ignore next */ []));
19583
+ /** Presentation-resolved sections, so the template stays declarative. */
19584
+ rows = computed(() => this.sections().map((section, index) => ({
19585
+ index,
19586
+ section,
19587
+ layout: sectionLayout(section, index),
19588
+ expanded: this.expandedSet().has(index),
19589
+ hintKey: sectionHintKey(section, this.expandedSet().has(index)),
19590
+ })), ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
19591
+ iconFor(key) {
19592
+ return this.icons().find(d => d.flyModuleIcon() === key)?.template;
19593
+ }
19594
+ toggle(index) {
19595
+ const next = new Set(this.expandedSet());
19596
+ if (!next.delete(index))
19597
+ next.add(index);
19598
+ this.userToggled.set(next);
19599
+ }
19600
+ select(module) {
19601
+ if (module.disabled)
19602
+ return;
19603
+ this.moduleSelected.emit(module.key);
19604
+ }
19605
+ sectionId(index) {
19606
+ return `fly-app-home-sec-${index}`;
19607
+ }
19608
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyAppHomeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
19609
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyAppHomeComponent, isStandalone: true, selector: "fly-app-home", inputs: { brandLabelKey: { classPropertyName: "brandLabelKey", publicName: "brandLabelKey", isSignal: true, isRequired: false, transformFunction: null }, titleKey: { classPropertyName: "titleKey", publicName: "titleKey", isSignal: true, isRequired: false, transformFunction: null }, subtitleKey: { classPropertyName: "subtitleKey", publicName: "subtitleKey", isSignal: true, isRequired: false, transformFunction: null }, sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: false, transformFunction: null }, modulesLabelKey: { classPropertyName: "modulesLabelKey", publicName: "modulesLabelKey", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { moduleSelected: "moduleSelected", brandSelected: "brandSelected" }, queries: [{ propertyName: "icons", predicate: FlyModuleIconDirective, isSignal: true }], ngImport: i0, template: "<div class=\"ah\">\r\n <header class=\"ah__util\">\r\n <button type=\"button\" class=\"ah__brand\" (click)=\"brandSelected.emit()\">\r\n <span class=\"ah__brand-mark\"><ng-content select=\"[app-home-brand]\" /></span>\r\n @if (brandLabelKey(); as key) {\r\n <span class=\"ah__brand-name\">{{ key | translate }}</span>\r\n }\r\n </button>\r\n <ng-content select=\"[app-home-actions]\" />\r\n </header>\r\n\r\n <main class=\"ah__main\">\r\n @if (titleKey() || subtitleKey()) {\r\n <section class=\"ah__hero\">\r\n @if (titleKey(); as key) {\r\n <h1 class=\"ah__title\">{{ key | translate }}</h1>\r\n }\r\n @if (subtitleKey(); as key) {\r\n <p class=\"ah__sub\">{{ key | translate }}</p>\r\n }\r\n </section>\r\n }\r\n\r\n @for (row of rows(); track row.index) {\r\n <section\r\n class=\"ah__sec\"\r\n [class.ah__sec--secondary]=\"row.index > 0\"\r\n [attr.aria-label]=\"row.section.titleKey ? null : (modulesLabelKey() | translate)\"\r\n >\r\n @if (row.section.titleKey; as titleKey) {\r\n @if (row.section.collapsible) {\r\n <button\r\n type=\"button\"\r\n class=\"ah__sec-hd\"\r\n (click)=\"toggle(row.index)\"\r\n [attr.aria-expanded]=\"row.expanded\"\r\n [attr.aria-controls]=\"sectionId(row.index)\"\r\n >\r\n <!-- Points end-ward when shut, down when open. `--open` wins over the RTL\r\n flip below because a downward chevron has no direction to mirror. -->\r\n <svg\r\n class=\"ah__sec-chev\"\r\n [class.ah__sec-chev--open]=\"row.expanded\"\r\n width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\r\n stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"\r\n >\r\n <polyline points=\"9 6 15 12 9 18\" />\r\n </svg>\r\n <span class=\"ah__sec-title\">{{ titleKey | translate }}</span>\r\n <span class=\"ah__sec-count\">{{ row.section.modules.length }}</span>\r\n @if (row.hintKey; as hint) {\r\n <span class=\"ah__sec-hint\">{{ hint | translate }}</span>\r\n }\r\n </button>\r\n } @else {\r\n <h2 class=\"ah__sec-title ah__sec-title--static\">{{ titleKey | translate }}</h2>\r\n }\r\n }\r\n\r\n @if (row.expanded) {\r\n <div class=\"ah__grid\" [class.ah__grid--compact]=\"row.layout === 'compact'\" [id]=\"sectionId(row.index)\">\r\n @for (mod of row.section.modules; track mod.key) {\r\n <button\r\n type=\"button\"\r\n class=\"ah__card\"\r\n [class.ah__card--compact]=\"row.layout === 'compact'\"\r\n [disabled]=\"mod.disabled\"\r\n (click)=\"select(mod)\"\r\n >\r\n <span class=\"ah__card-ico\">\r\n @if (iconFor(mod.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (mod.iconClass) {\r\n <i [class]=\"mod.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n\r\n @if (row.layout === 'compact') {\r\n <span class=\"ah__card-main\">\r\n <span class=\"ah__card-title\">{{ mod.labelKey | translate }}</span>\r\n @if (mod.descKey; as descKey) {\r\n <span class=\"ah__card-desc\">{{ descKey | translate }}</span>\r\n }\r\n </span>\r\n <svg\r\n class=\"ah__card-arrow\" width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2.6\" stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\" aria-hidden=\"true\"\r\n >\r\n <polyline points=\"9 6 15 12 9 18\" />\r\n </svg>\r\n } @else {\r\n <span class=\"ah__card-title ah__card-title--feature\">{{ mod.labelKey | translate }}</span>\r\n @if (mod.descKey; as descKey) {\r\n <span class=\"ah__card-desc ah__card-desc--feature\">{{ descKey | translate }}</span>\r\n }\r\n <span class=\"ah__card-open\">\r\n {{ 'ui.action.open' | translate }}\r\n <svg\r\n class=\"ah__card-arrow\" width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2.6\" stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\" aria-hidden=\"true\"\r\n >\r\n <polyline points=\"9 6 15 12 9 18\" />\r\n </svg>\r\n </span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n }\r\n </section>\r\n }\r\n </main>\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;min-height:100%;container-type:inline-size}.ah{display:flex;flex-direction:column;flex:1;min-height:100%}.ah__util{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-4);padding:var(--sp-4) var(--sp-6)}.ah__brand{display:inline-flex;align-items:center;gap:var(--sp-2);padding:0;border:0;background:none;color:var(--ink);font-family:inherit;cursor:pointer}.ah__brand:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.ah__brand-name{font-size:var(--text-md);font-weight:var(--fw-semibold);letter-spacing:-.01em}.ah__main{flex:1;inline-size:100%;max-inline-size:1120px;margin-inline:auto;padding:56px 40px;display:flex;flex-direction:column;justify-content:center;gap:44px}.ah__hero{max-inline-size:640px}.ah__title{margin:0 0 var(--sp-3);font-size:44px;font-weight:var(--fw-semibold);letter-spacing:-.03em;text-wrap:balance;background:linear-gradient(180deg,var(--ink),var(--ink-2));-webkit-background-clip:text;background-clip:text;color:transparent}.ah__sub{margin:0;max-inline-size:560px;font-size:var(--text-lg);line-height:1.55;color:var(--ink-3);text-wrap:pretty}.ah__sec{display:flex;flex-direction:column;gap:var(--sp-4)}.ah__sec--secondary{padding-block-start:28px;border-block-start:1px solid var(--line)}.ah__sec-hd{display:flex;align-items:center;gap:var(--sp-3);inline-size:100%;padding:0;background:none;border:0;cursor:pointer;font-family:inherit;color:var(--ink);text-align:start}.ah__sec-hd:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.ah__sec-chev{flex-shrink:0;color:var(--ink-3);transition:transform var(--t-overlay)}.ah__sec-title{font-size:var(--text-md);font-weight:var(--fw-semibold);letter-spacing:-.01em}.ah__sec-title--static{margin:0;color:var(--ink-3);font-size:var(--text-xs);letter-spacing:var(--tracking-wide);text-transform:uppercase}.ah__sec-count{display:inline-grid;place-items:center;min-inline-size:22px;block-size:20px;padding-inline:6px;border-radius:999px;background:var(--bg-3);border:1px solid var(--line);font-size:var(--text-2xs);font-weight:var(--fw-medium);color:var(--ink-3)}.ah__sec-hint{margin-inline-start:auto;font-size:var(--text-xs);color:var(--ink-3)}.ah__grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--sp-3)}.ah__card{display:flex;flex-direction:column;align-items:stretch;position:relative;overflow:hidden;text-align:start;padding:22px 22px 18px;background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-lg);color:inherit;font-family:inherit;cursor:pointer;transition:transform var(--t-overlay),box-shadow var(--t-overlay),border-color var(--t-overlay)}.ah__card:before{content:\"\";position:absolute;inset:0;pointer-events:none;border-radius:inherit;padding:1px;background:var(--grad);-webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;mask-composite:exclude;opacity:0;transition:opacity var(--t-overlay)}.ah__card:hover:not(:disabled){transform:translateY(-2px);border-color:transparent;box-shadow:var(--shadow-filter)}.ah__card:hover:not(:disabled):before{opacity:1}.ah__card:focus-visible{outline:0;border-color:transparent;box-shadow:0 0 0 3px var(--accent-soft),var(--shadow-filter)}.ah__card:focus-visible:before{opacity:1}.ah__card:disabled{cursor:default;opacity:.5}.ah__card-ico{display:grid;place-items:center;inline-size:40px;block-size:40px;margin-block-end:18px;border-radius:11px;background:var(--bg-3);border:0;color:var(--ink);font-size:var(--icon-lg);transition:background var(--t-overlay),color var(--t-overlay)}.ah__card:hover:not(:disabled) .ah__card-ico,.ah__card:focus-visible .ah__card-ico{background:var(--grad);color:var(--ink-inverse)}.ah__card-title{font-size:var(--text-sm);font-weight:var(--fw-semibold);letter-spacing:-.01em;color:var(--ink)}.ah__card-title--feature{margin-block-end:4px;font-size:18px;letter-spacing:-.015em}.ah__card-desc{font-size:var(--text-xs);line-height:1.45;color:var(--ink-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ah__card-desc--feature{margin-block-end:18px;font-size:var(--text-sm);line-height:1.5;text-wrap:pretty;overflow:visible;white-space:normal}.ah__card-open{display:inline-flex;align-items:center;gap:4px;margin-block-start:auto;font-size:var(--text-2xs);color:var(--ink-3);transition:color var(--t-overlay),gap var(--t-overlay)}.ah__card:hover:not(:disabled) .ah__card-open,.ah__card:focus-visible .ah__card-open{color:var(--accent);gap:8px}.ah__card-arrow{flex-shrink:0;transition:color var(--t-overlay),transform var(--t-overlay)}.ah__card--compact{flex-direction:row;align-items:center;gap:var(--sp-3);padding:var(--sp-3) var(--sp-4)}.ah__card--compact .ah__card-ico{inline-size:34px;block-size:34px;margin-block-end:0;flex-shrink:0;border-radius:9px;font-size:var(--icon-md)}.ah__card-main{display:flex;flex-direction:column;gap:2px;flex:1;min-inline-size:0}.ah__card--compact .ah__card-arrow{color:var(--ink-4)}.ah__card--compact:hover:not(:disabled) .ah__card-arrow{color:var(--accent);transform:translate(2px)}:host-context([dir=rtl]) .ah__card-arrow{transform:scaleX(-1)}:host-context([dir=rtl]) .ah__sec-chev:not(.ah__sec-chev--open){transform:scaleX(-1)}:host-context([dir=rtl]) .ah__card--compact:hover:not(:disabled) .ah__card-arrow{transform:scaleX(-1) translate(2px)}.ah__sec-chev--open{transform:rotate(90deg)}@container (width <= 900px){.ah__grid{grid-template-columns:repeat(2,minmax(0,1fr))}.ah__main{padding:40px 24px;gap:36px}.ah__title{font-size:36px}.ah__util{padding:var(--sp-3) var(--sp-5)}}@media(width<=900px){.ah__grid{grid-template-columns:repeat(2,minmax(0,1fr))}.ah__main{padding:40px 24px;gap:36px}.ah__title{font-size:36px}.ah__util{padding:var(--sp-3) var(--sp-5)}}@container (width <= 560px){.ah__grid{grid-template-columns:1fr}.ah__title{font-size:30px}.ah__sub{font-size:var(--text-sm)}.ah__sec-hint{display:none}}@media(width<=560px){.ah__grid{grid-template-columns:1fr}.ah__title{font-size:30px}.ah__sub{font-size:var(--text-sm)}.ah__sec-hint{display:none}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
19610
+ }
19611
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyAppHomeComponent, decorators: [{
19612
+ type: Component,
19613
+ args: [{ selector: 'fly-app-home', standalone: true, imports: [NgTemplateOutlet, TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"ah\">\r\n <header class=\"ah__util\">\r\n <button type=\"button\" class=\"ah__brand\" (click)=\"brandSelected.emit()\">\r\n <span class=\"ah__brand-mark\"><ng-content select=\"[app-home-brand]\" /></span>\r\n @if (brandLabelKey(); as key) {\r\n <span class=\"ah__brand-name\">{{ key | translate }}</span>\r\n }\r\n </button>\r\n <ng-content select=\"[app-home-actions]\" />\r\n </header>\r\n\r\n <main class=\"ah__main\">\r\n @if (titleKey() || subtitleKey()) {\r\n <section class=\"ah__hero\">\r\n @if (titleKey(); as key) {\r\n <h1 class=\"ah__title\">{{ key | translate }}</h1>\r\n }\r\n @if (subtitleKey(); as key) {\r\n <p class=\"ah__sub\">{{ key | translate }}</p>\r\n }\r\n </section>\r\n }\r\n\r\n @for (row of rows(); track row.index) {\r\n <section\r\n class=\"ah__sec\"\r\n [class.ah__sec--secondary]=\"row.index > 0\"\r\n [attr.aria-label]=\"row.section.titleKey ? null : (modulesLabelKey() | translate)\"\r\n >\r\n @if (row.section.titleKey; as titleKey) {\r\n @if (row.section.collapsible) {\r\n <button\r\n type=\"button\"\r\n class=\"ah__sec-hd\"\r\n (click)=\"toggle(row.index)\"\r\n [attr.aria-expanded]=\"row.expanded\"\r\n [attr.aria-controls]=\"sectionId(row.index)\"\r\n >\r\n <!-- Points end-ward when shut, down when open. `--open` wins over the RTL\r\n flip below because a downward chevron has no direction to mirror. -->\r\n <svg\r\n class=\"ah__sec-chev\"\r\n [class.ah__sec-chev--open]=\"row.expanded\"\r\n width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\r\n stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"\r\n >\r\n <polyline points=\"9 6 15 12 9 18\" />\r\n </svg>\r\n <span class=\"ah__sec-title\">{{ titleKey | translate }}</span>\r\n <span class=\"ah__sec-count\">{{ row.section.modules.length }}</span>\r\n @if (row.hintKey; as hint) {\r\n <span class=\"ah__sec-hint\">{{ hint | translate }}</span>\r\n }\r\n </button>\r\n } @else {\r\n <h2 class=\"ah__sec-title ah__sec-title--static\">{{ titleKey | translate }}</h2>\r\n }\r\n }\r\n\r\n @if (row.expanded) {\r\n <div class=\"ah__grid\" [class.ah__grid--compact]=\"row.layout === 'compact'\" [id]=\"sectionId(row.index)\">\r\n @for (mod of row.section.modules; track mod.key) {\r\n <button\r\n type=\"button\"\r\n class=\"ah__card\"\r\n [class.ah__card--compact]=\"row.layout === 'compact'\"\r\n [disabled]=\"mod.disabled\"\r\n (click)=\"select(mod)\"\r\n >\r\n <span class=\"ah__card-ico\">\r\n @if (iconFor(mod.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (mod.iconClass) {\r\n <i [class]=\"mod.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n\r\n @if (row.layout === 'compact') {\r\n <span class=\"ah__card-main\">\r\n <span class=\"ah__card-title\">{{ mod.labelKey | translate }}</span>\r\n @if (mod.descKey; as descKey) {\r\n <span class=\"ah__card-desc\">{{ descKey | translate }}</span>\r\n }\r\n </span>\r\n <svg\r\n class=\"ah__card-arrow\" width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2.6\" stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\" aria-hidden=\"true\"\r\n >\r\n <polyline points=\"9 6 15 12 9 18\" />\r\n </svg>\r\n } @else {\r\n <span class=\"ah__card-title ah__card-title--feature\">{{ mod.labelKey | translate }}</span>\r\n @if (mod.descKey; as descKey) {\r\n <span class=\"ah__card-desc ah__card-desc--feature\">{{ descKey | translate }}</span>\r\n }\r\n <span class=\"ah__card-open\">\r\n {{ 'ui.action.open' | translate }}\r\n <svg\r\n class=\"ah__card-arrow\" width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2.6\" stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\" aria-hidden=\"true\"\r\n >\r\n <polyline points=\"9 6 15 12 9 18\" />\r\n </svg>\r\n </span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n }\r\n </section>\r\n }\r\n </main>\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;min-height:100%;container-type:inline-size}.ah{display:flex;flex-direction:column;flex:1;min-height:100%}.ah__util{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-4);padding:var(--sp-4) var(--sp-6)}.ah__brand{display:inline-flex;align-items:center;gap:var(--sp-2);padding:0;border:0;background:none;color:var(--ink);font-family:inherit;cursor:pointer}.ah__brand:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.ah__brand-name{font-size:var(--text-md);font-weight:var(--fw-semibold);letter-spacing:-.01em}.ah__main{flex:1;inline-size:100%;max-inline-size:1120px;margin-inline:auto;padding:56px 40px;display:flex;flex-direction:column;justify-content:center;gap:44px}.ah__hero{max-inline-size:640px}.ah__title{margin:0 0 var(--sp-3);font-size:44px;font-weight:var(--fw-semibold);letter-spacing:-.03em;text-wrap:balance;background:linear-gradient(180deg,var(--ink),var(--ink-2));-webkit-background-clip:text;background-clip:text;color:transparent}.ah__sub{margin:0;max-inline-size:560px;font-size:var(--text-lg);line-height:1.55;color:var(--ink-3);text-wrap:pretty}.ah__sec{display:flex;flex-direction:column;gap:var(--sp-4)}.ah__sec--secondary{padding-block-start:28px;border-block-start:1px solid var(--line)}.ah__sec-hd{display:flex;align-items:center;gap:var(--sp-3);inline-size:100%;padding:0;background:none;border:0;cursor:pointer;font-family:inherit;color:var(--ink);text-align:start}.ah__sec-hd:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.ah__sec-chev{flex-shrink:0;color:var(--ink-3);transition:transform var(--t-overlay)}.ah__sec-title{font-size:var(--text-md);font-weight:var(--fw-semibold);letter-spacing:-.01em}.ah__sec-title--static{margin:0;color:var(--ink-3);font-size:var(--text-xs);letter-spacing:var(--tracking-wide);text-transform:uppercase}.ah__sec-count{display:inline-grid;place-items:center;min-inline-size:22px;block-size:20px;padding-inline:6px;border-radius:999px;background:var(--bg-3);border:1px solid var(--line);font-size:var(--text-2xs);font-weight:var(--fw-medium);color:var(--ink-3)}.ah__sec-hint{margin-inline-start:auto;font-size:var(--text-xs);color:var(--ink-3)}.ah__grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--sp-3)}.ah__card{display:flex;flex-direction:column;align-items:stretch;position:relative;overflow:hidden;text-align:start;padding:22px 22px 18px;background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-lg);color:inherit;font-family:inherit;cursor:pointer;transition:transform var(--t-overlay),box-shadow var(--t-overlay),border-color var(--t-overlay)}.ah__card:before{content:\"\";position:absolute;inset:0;pointer-events:none;border-radius:inherit;padding:1px;background:var(--grad);-webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;mask-composite:exclude;opacity:0;transition:opacity var(--t-overlay)}.ah__card:hover:not(:disabled){transform:translateY(-2px);border-color:transparent;box-shadow:var(--shadow-filter)}.ah__card:hover:not(:disabled):before{opacity:1}.ah__card:focus-visible{outline:0;border-color:transparent;box-shadow:0 0 0 3px var(--accent-soft),var(--shadow-filter)}.ah__card:focus-visible:before{opacity:1}.ah__card:disabled{cursor:default;opacity:.5}.ah__card-ico{display:grid;place-items:center;inline-size:40px;block-size:40px;margin-block-end:18px;border-radius:11px;background:var(--bg-3);border:0;color:var(--ink);font-size:var(--icon-lg);transition:background var(--t-overlay),color var(--t-overlay)}.ah__card:hover:not(:disabled) .ah__card-ico,.ah__card:focus-visible .ah__card-ico{background:var(--grad);color:var(--ink-inverse)}.ah__card-title{font-size:var(--text-sm);font-weight:var(--fw-semibold);letter-spacing:-.01em;color:var(--ink)}.ah__card-title--feature{margin-block-end:4px;font-size:18px;letter-spacing:-.015em}.ah__card-desc{font-size:var(--text-xs);line-height:1.45;color:var(--ink-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ah__card-desc--feature{margin-block-end:18px;font-size:var(--text-sm);line-height:1.5;text-wrap:pretty;overflow:visible;white-space:normal}.ah__card-open{display:inline-flex;align-items:center;gap:4px;margin-block-start:auto;font-size:var(--text-2xs);color:var(--ink-3);transition:color var(--t-overlay),gap var(--t-overlay)}.ah__card:hover:not(:disabled) .ah__card-open,.ah__card:focus-visible .ah__card-open{color:var(--accent);gap:8px}.ah__card-arrow{flex-shrink:0;transition:color var(--t-overlay),transform var(--t-overlay)}.ah__card--compact{flex-direction:row;align-items:center;gap:var(--sp-3);padding:var(--sp-3) var(--sp-4)}.ah__card--compact .ah__card-ico{inline-size:34px;block-size:34px;margin-block-end:0;flex-shrink:0;border-radius:9px;font-size:var(--icon-md)}.ah__card-main{display:flex;flex-direction:column;gap:2px;flex:1;min-inline-size:0}.ah__card--compact .ah__card-arrow{color:var(--ink-4)}.ah__card--compact:hover:not(:disabled) .ah__card-arrow{color:var(--accent);transform:translate(2px)}:host-context([dir=rtl]) .ah__card-arrow{transform:scaleX(-1)}:host-context([dir=rtl]) .ah__sec-chev:not(.ah__sec-chev--open){transform:scaleX(-1)}:host-context([dir=rtl]) .ah__card--compact:hover:not(:disabled) .ah__card-arrow{transform:scaleX(-1) translate(2px)}.ah__sec-chev--open{transform:rotate(90deg)}@container (width <= 900px){.ah__grid{grid-template-columns:repeat(2,minmax(0,1fr))}.ah__main{padding:40px 24px;gap:36px}.ah__title{font-size:36px}.ah__util{padding:var(--sp-3) var(--sp-5)}}@media(width<=900px){.ah__grid{grid-template-columns:repeat(2,minmax(0,1fr))}.ah__main{padding:40px 24px;gap:36px}.ah__title{font-size:36px}.ah__util{padding:var(--sp-3) var(--sp-5)}}@container (width <= 560px){.ah__grid{grid-template-columns:1fr}.ah__title{font-size:30px}.ah__sub{font-size:var(--text-sm)}.ah__sec-hint{display:none}}@media(width<=560px){.ah__grid{grid-template-columns:1fr}.ah__title{font-size:30px}.ah__sub{font-size:var(--text-sm)}.ah__sec-hint{display:none}}\n"] }]
19614
+ }], propDecorators: { brandLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "brandLabelKey", required: false }] }], titleKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleKey", required: false }] }], subtitleKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitleKey", required: false }] }], sections: [{ type: i0.Input, args: [{ isSignal: true, alias: "sections", required: false }] }], modulesLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "modulesLabelKey", required: false }] }], moduleSelected: [{ type: i0.Output, args: ["moduleSelected"] }], brandSelected: [{ type: i0.Output, args: ["brandSelected"] }], icons: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => FlyModuleIconDirective), { isSignal: true }] }] } });
19615
+
18999
19616
  /**
19000
19617
  * Resolve which tab is active: the selected id when it exists in the tab set,
19001
19618
  * otherwise the first tab (or null with no tabs). Pure so the fallback rules
@@ -19959,7 +20576,7 @@ class FlyFormSectionComponent {
19959
20576
  }
19960
20577
  </div>
19961
20578
  <div class="sec__c"><ng-content /></div>
19962
- `, isInline: true, styles: [":host{display:grid;grid-template-columns:140px 1fr;gap:24px;padding:22px 0;border-bottom:1px solid var(--line-3)}:host(:last-of-type){border-bottom:0}.sec__l{font-size:var(--text-xs);font-weight:var(--fw-medium);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-3);padding-top:8px;position:sticky;top:8px;align-self:start}.sec__c{min-width:0}@media(width<=720px){:host{grid-template-columns:1fr;gap:8px}.sec__l{padding-top:0;position:static}}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
20579
+ `, isInline: true, styles: [":host{display:grid;grid-template-columns:140px 1fr;gap:24px;padding:22px 0;border-bottom:1px solid var(--line-3)}:host(:last-of-type){border-bottom:0}.sec__l{font-size:var(--text-xs);font-weight:var(--fw-medium);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-3);padding-top:8px;position:sticky;top:8px;align-self:start}.sec__c{min-width:0}@container (width <= 720px){:host{grid-template-columns:1fr;gap:8px}.sec__l{padding-top:0;position:static}}@media(width<=720px){:host{grid-template-columns:1fr;gap:8px}.sec__l{padding-top:0;position:static}}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
19963
20580
  }
19964
20581
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyFormSectionComponent, decorators: [{
19965
20582
  type: Component,
@@ -19970,7 +20587,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
19970
20587
  }
19971
20588
  </div>
19972
20589
  <div class="sec__c"><ng-content /></div>
19973
- `, styles: [":host{display:grid;grid-template-columns:140px 1fr;gap:24px;padding:22px 0;border-bottom:1px solid var(--line-3)}:host(:last-of-type){border-bottom:0}.sec__l{font-size:var(--text-xs);font-weight:var(--fw-medium);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-3);padding-top:8px;position:sticky;top:8px;align-self:start}.sec__c{min-width:0}@media(width<=720px){:host{grid-template-columns:1fr;gap:8px}.sec__l{padding-top:0;position:static}}\n"] }]
20590
+ `, styles: [":host{display:grid;grid-template-columns:140px 1fr;gap:24px;padding:22px 0;border-bottom:1px solid var(--line-3)}:host(:last-of-type){border-bottom:0}.sec__l{font-size:var(--text-xs);font-weight:var(--fw-medium);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-3);padding-top:8px;position:sticky;top:8px;align-self:start}.sec__c{min-width:0}@container (width <= 720px){:host{grid-template-columns:1fr;gap:8px}.sec__l{padding-top:0;position:static}}@media(width<=720px){:host{grid-template-columns:1fr;gap:8px}.sec__l{padding-top:0;position:static}}\n"] }]
19974
20591
  }], propDecorators: { labelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelKey", required: false }] }] } });
19975
20592
  /**
19976
20593
  * Responsive field grid (port of the legacy `.dr__grid/--2/--3`): 1–3 equal
@@ -21224,5 +21841,5 @@ const AUDIENCE_ERROR_CODES = {
21224
21841
  * Generated bundle index. Do not edit.
21225
21842
  */
21226
21843
 
21227
- export { AGENT_DRAG_MIME, AGENT_PAYLOAD_VERSION, APP_LOOKUP, AUDIENCE_ERROR_CODES, AUDIENCE_LIMITS, AUDIENCE_PRESETS, AUDIENCE_TERM_KINDS, AgentActionBus, AgentActionUnsupportedDispatchError, AgentCommandRegistry, AgentDropRegistry, AgentFlightAnimator, AgentLookupRegistry, AgentPayloadOversizeError, AudienceBuilderComponent, AuthService, BilingualFieldComponent, CRON_MODES, CRON_WEEKDAYS, ContextMenuComponent, DEFAULT_AGENT_PAYLOAD_LIMITS, DEFAULT_FLY_THEME_MODE, DS_BASELINE_LOCALES, DashboardKpiComponent, DialogResult, EntityLookupComponent, FLYOS_LAUNCH_EVENT, FLYOS_LAUNCH_REQUEST_EVENT, FLYOS_REMOTE_ROUTE_EVENT, FLY_ACCENT_PROPERTY, FLY_ADMIN_ROLES, FLY_CHUNK_RELOAD_FLAG, FLY_COMPACT_THRESHOLD, FLY_COUNTRIES, FLY_EMOJI_BY_ID, FLY_EMOJI_CATEGORIES, FLY_EMOJI_CATEGORY_BY_ID, FLY_EMOJI_DEFAULT_CATEGORIES, FLY_EMOJI_PACK, FLY_EMOJI_PACK_MISSING_MESSAGE, FLY_EMOJI_PREVIEW_COUNT, FLY_EMPTY_VALUE, FLY_LOCALE_CATALOG, FLY_NF_CACHE_HEAL_FLAG, FLY_RELOAD_DEBOUNCE_MS, FLY_REMOTE_BASE_PATH, FLY_REMOTE_CONTEXT_EVENT, FLY_REMOTE_CONTEXT_STORE_KEY, FLY_REMOTE_ROUTES, FLY_SEARCH_DEBOUNCE_MS, FLY_SKIN_TONES, FLY_STANDALONE_AUTH_CONFIG, FLY_THEME_MODE_IDS, FLY_WINDOW_HELP_HINT_EVENT, FLY_WINDOW_PAGE_TITLE_EVENT, FlyAchievementProgressComponent, FlyActionMenuComponent, FlyActionStackComponent, FlyAgentDraggableDirective, FlyAnimatedEmojiComponent, FlyAppTopbarComponent, FlyAuthLiveRefresh, FlyBadgeWallComponent, FlyBlockUiComponent, FlyBreadcrumbComponent, FlyButtonComponent, FlyBytesPipe, FlyCaptchaComponent, FlyCardActionsComponent, FlyCardBodyComponent, FlyCardComponent, FlyCardFooterComponent, FlyCardGridComponent, FlyCardMediaComponent, FlyCardMetaComponent, FlyCardStatusComponent, FlyCardTitleComponent, FlyCellDirective, FlyCheckboxComponent, FlyChipComponent, FlyChunkReloadErrorHandler, FlyClickOutsideDirective, FlyCollapsibleComponent, FlyCommentThreadComponent, FlyCompactNumberPipe, FlyConfirmDialogComponent, FlyControlDirective, FlyCronBuilderComponent, FlyDataTableComponent, FlyDatePipe, FlyDateTimePipe, FlyDebouncer, FlyDecimalNumberPipe, FlyDetailCardComponent, FlyDetailShellComponent, FlyDrawerComponent, FlyDurationPipe, FlyDynamicFormComponent, FlyEmojiPickerComponent, FlyFieldComponent, FlyFileUploadComponent, FlyFilterPanelComponent, FlyFormBannerComponent, FlyFormFooterComponent, FlyFormGridComponent, FlyFormSectionComponent, FlyFullNumberPipe, FlyGanttComponent, FlyHubClient, FlyIconButtonComponent, FlyIfAdminDirective, FlyIfRoleDirective, FlyImageUploadComponent, FlyLeaderboardComponent, FlyMetaListComponent, FlyModalComponent, FlyModerationQueueComponent, FlyModuleIconDirective, FlyPaginationComponent, FlyPeoplePickerComponent, FlyPointsTierComponent, FlyProgressComponent, FlyRelativeTimePipe, FlyRemoteContextService, FlyRemoteRouter, FlyRemoteRouterOutletComponent, FlySearchInputComponent, FlySectionHeaderComponent, FlySecureSrcDirective, FlySegmentedComponent, FlySelectComponent, FlySkeletonComponent, FlySliderComponent, FlySparklineComponent, FlySpinnerComponent, FlyStandaloneAuthCallbackComponent, FlyStandaloneAuthService, FlyStateMessageComponent, FlyDynamicFormComponent as FlySurveyFormComponent, FlyTabComponent, FlyTabsComponent, FlyTagsInputComponent, FlyThemeService, FlyTimePipe, FlyToastService, FlyToggleComponent, FlyTooltipDirective, FlyTreeNavComponent, FlyTypeaheadComponent, FlyWindowHelpService, FlyWindowTitleService, FlyosPendingLaunchesGlobalKey, FlyosShellHandlesLaunchRequestsGlobalKey, FlyosShellOwnsHistoryGlobalKey, GANTT_ZOOMS, I18nService, LAUNCH_CONTEXT, MessageBoxButtons, MessageBoxComponent, MessageBoxIcon, MessageBoxService, MockAuthService, OverlayStack, PRESENCE_COLORS, PageHeaderComponent, RTL_LOCALE_SET, SHARE_ORG_CHART_SYSTEM_KEY_APPS, SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT, SHARE_PANEL_DEFAULT_FILE_LEVELS, STATE_DEFAULT_ICONS, STATE_DEFAULT_MESSAGE_KEYS, STEP_UP_CODE, STEP_UP_REAUTH_HANDLER, STEP_UP_RETURN_KEY, SUPPORTED_AGENT_PAYLOAD_VERSIONS, SharePanelComponent, SourceAppResolver, StandaloneWindowManagerService, StatusBadgeComponent, StepUpService, ToastHostComponent, TranslatePipe, WINDOW_DATA, WINDOW_HELP_HINT, WindowManagerService, applySkinTone, applySuggestion, ariaSort, buildCron, canConfirm, canGoNext, canGoPrev, captureFocus, clampPage, clampSliderValue, connectRemoteLaunch, enterActivatesNatively, filterGroup, filterSuggestions, findLocaleByDialect, findLocaleByPrefix, firstEnabledIndex, firstModuleIndex, flattenModules, flyAdminGuard, flyCaptchaBase64Utf8, flyCaptchaBuildToken, flyCaptchaSha256Hex, flyCompactNumber, flyDebounced, flyDecimalNumber, flyDownloadBlob, flyDuration, flyEmojiFor, flyEmojiIsTonable, flyEmojiPackUrl, flyExportFileName, flyFirstEnabledIndex, flyFormatBytes, flyFormatDate, flyFormatDateTime, flyFormatTime, flyFullNumber, flyNextEnabledIndex, flyNextSegmentIndex, flyRelativeTime, flyResolveActiveTab, flyRoleGuard, flySignedCompact, flySkinToneLabelKey, flyStandaloneAuthGuard, flyStandaloneAuthInterceptor, flyToDateOnly, hasAnyRoleIn, healNativeFederationCacheOnce, isAccentColor, isChunkLoadError, isFlyAuthStorePopulated, isNativeFederationCacheError, isRtlLocale, isRtlLocaleEntry, isValidCron, isValidSingleField, loadRemoteStyles, matchFlyRoutePattern, nextEnabledIndex, nextModuleIndex, nextSegmentIndex, nextSort, normalizeFlyTheme, normalizeRoles, overlayStack, parseCron, parseStepUpChallenge, parseTags, presenceColorFor, printConsoleSecurityWarning, provideFlyChunkReloadRecovery, provideFlyEmojiPack, provideFlyStandaloneAuth, reloadOnceForChunkError, requestAppLaunch, resolveActiveModule, resolveActiveSection, resolveActiveTab, resolveCellValue, resolveStateIcon, resolveStateMessageKey, restoreFocus, sameTags, sliderTrackGradient, sparklinePath, sparklinePoints, stepUpInterceptor, stepUpReturnKey, trimAgentPayload, trimAgentString, unloadRemoteStyles, utf8ByteLength, validateAgentPayload, warnIfEmbeddedSessionMissing };
21844
+ export { AGENT_DRAG_MIME, AGENT_PAYLOAD_VERSION, APP_LOOKUP, AUDIENCE_ERROR_CODES, AUDIENCE_LIMITS, AUDIENCE_PRESETS, AUDIENCE_TERM_KINDS, AgentActionBus, AgentActionUnsupportedDispatchError, AgentCommandRegistry, AgentDropRegistry, AgentFlightAnimator, AgentLookupRegistry, AgentPayloadOversizeError, AudienceBuilderComponent, AuthService, BilingualFieldComponent, CRON_MODES, CRON_WEEKDAYS, ContextMenuComponent, DEFAULT_AGENT_PAYLOAD_LIMITS, DEFAULT_FLY_THEME_MODE, DS_BASELINE_LOCALES, DashboardKpiComponent, DialogResult, EntityLookupComponent, FLYOS_LAUNCH_EVENT, FLYOS_LAUNCH_REQUEST_EVENT, FLYOS_REMOTE_ROUTE_EVENT, FLY_ACCENT_PROPERTY, FLY_ADMIN_ROLES, FLY_CHUNK_RELOAD_FLAG, FLY_COMPACT_THRESHOLD, FLY_COUNTRIES, FLY_EMOJI_BY_ID, FLY_EMOJI_CATEGORIES, FLY_EMOJI_CATEGORY_BY_ID, FLY_EMOJI_DEFAULT_CATEGORIES, FLY_EMOJI_PACK, FLY_EMOJI_PACK_MISSING_MESSAGE, FLY_EMOJI_PREVIEW_COUNT, FLY_EMPTY_VALUE, FLY_LOCALE_CATALOG, FLY_NF_CACHE_HEAL_FLAG, FLY_RELOAD_DEBOUNCE_MS, FLY_RELOAD_REARM_MS, FLY_REMOTE_BASE_PATH, FLY_REMOTE_CONTEXT_EVENT, FLY_REMOTE_CONTEXT_STORE_KEY, FLY_REMOTE_ROUTES, FLY_SEARCH_DEBOUNCE_MS, FLY_SKIN_TONES, FLY_STANDALONE_AUTH_CONFIG, FLY_THEME_MODE_IDS, FLY_WINDOW_HELP_HINT_EVENT, FLY_WINDOW_PAGE_TITLE_EVENT, FlyAchievementProgressComponent, FlyActionMenuComponent, FlyActionStackComponent, FlyAgentDraggableDirective, FlyAnimatedEmojiComponent, FlyAppHomeComponent, FlyAppTopbarComponent, FlyAuthLiveRefresh, FlyBadgeWallComponent, FlyBlockUiComponent, FlyBreadcrumbComponent, FlyButtonComponent, FlyBytesPipe, FlyCaptchaComponent, FlyCardActionsComponent, FlyCardBodyComponent, FlyCardComponent, FlyCardFooterComponent, FlyCardGridComponent, FlyCardMediaComponent, FlyCardMetaComponent, FlyCardStatusComponent, FlyCardTitleComponent, FlyCellDirective, FlyCheckboxComponent, FlyChipComponent, FlyChunkReloadErrorHandler, FlyClickOutsideDirective, FlyCollapsibleComponent, FlyCommentThreadComponent, FlyCompactNumberPipe, FlyConfirmDialogComponent, FlyControlDirective, FlyCronBuilderComponent, FlyDataTableComponent, FlyDatePipe, FlyDateTimePipe, FlyDebouncer, FlyDecimalNumberPipe, FlyDetailCardComponent, FlyDetailShellComponent, FlyDrawerComponent, FlyDurationPipe, FlyDynamicFormComponent, FlyEmojiPickerComponent, FlyFieldComponent, FlyFileUploadComponent, FlyFilterPanelComponent, FlyFormBannerComponent, FlyFormFooterComponent, FlyFormGridComponent, FlyFormSectionComponent, FlyFullNumberPipe, FlyGanttComponent, FlyHubClient, FlyIconButtonComponent, FlyIfAdminDirective, FlyIfRoleDirective, FlyImageUploadComponent, FlyLeaderboardComponent, FlyMetaListComponent, FlyModalComponent, FlyModerationQueueComponent, FlyModuleIconDirective, FlyPaginationComponent, FlyPeoplePickerComponent, FlyPointsTierComponent, FlyProgressComponent, FlyRelativeTimePipe, FlyRemoteContextService, FlyRemoteRouter, FlyRemoteRouterOutletComponent, FlySearchInputComponent, FlySectionHeaderComponent, FlySecureSrcDirective, FlySegmentedComponent, FlySelectComponent, FlySkeletonComponent, FlySliderComponent, FlySparklineComponent, FlySpinnerComponent, FlyStandaloneAuthCallbackComponent, FlyStandaloneAuthService, FlyStateMessageComponent, FlyDynamicFormComponent as FlySurveyFormComponent, FlyTabComponent, FlyTabsComponent, FlyTagsInputComponent, FlyThemeService, FlyTimePipe, FlyToastService, FlyToggleComponent, FlyTooltipDirective, FlyTreeNavComponent, FlyTypeaheadComponent, FlyUserDirectoryService, FlyWindowHelpService, FlyWindowTitleService, FlyosPendingLaunchesGlobalKey, FlyosShellHandlesLaunchRequestsGlobalKey, FlyosShellOwnsHistoryGlobalKey, GANTT_ZOOMS, I18nService, LAUNCH_CONTEXT, MessageBoxButtons, MessageBoxComponent, MessageBoxIcon, MessageBoxService, MockAuthService, OverlayStack, PRESENCE_COLORS, PageHeaderComponent, RTL_LOCALE_SET, SHARE_ORG_CHART_SYSTEM_KEY_APPS, SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT, SHARE_PANEL_DEFAULT_FILE_LEVELS, STATE_DEFAULT_ICONS, STATE_DEFAULT_MESSAGE_KEYS, STEP_UP_CODE, STEP_UP_REAUTH_HANDLER, STEP_UP_RETURN_KEY, SUPPORTED_AGENT_PAYLOAD_VERSIONS, SharePanelComponent, SourceAppResolver, StandaloneWindowManagerService, StatusBadgeComponent, StepUpService, ToastHostComponent, TranslatePipe, WINDOW_DATA, WINDOW_HELP_HINT, WindowManagerService, applySkinTone, applySuggestion, ariaSort, buildCron, canConfirm, canGoNext, canGoPrev, captureFocus, clampPage, clampSliderValue, connectRemoteLaunch, enterActivatesNatively, filterGroup, filterSuggestions, findLocaleByDialect, findLocaleByPrefix, firstEnabledIndex, firstModuleIndex, flattenModules, flyAdminGuard, flyCaptchaBase64Utf8, flyCaptchaBuildToken, flyCaptchaSha256Hex, flyCompactNumber, flyDebounced, flyDecimalNumber, flyDownloadBlob, flyDuration, flyEmojiFor, flyEmojiIsTonable, flyEmojiPackUrl, flyExportFileName, flyFirstEnabledIndex, flyFormatBytes, flyFormatDate, flyFormatDateTime, flyFormatTime, flyFullNumber, flyNextEnabledIndex, flyNextSegmentIndex, flyRelativeTime, flyResolveActiveTab, flyRoleGuard, flySignedCompact, flySkinToneLabelKey, flyStandaloneAuthGuard, flyStandaloneAuthInterceptor, flyToDateOnly, hasAnyRoleIn, healNativeFederationCacheOnce, initialExpanded, isAccentColor, isChunkLoadError, isFlyAuthStorePopulated, isNativeFederationCacheError, isRtlLocale, isRtlLocaleEntry, isValidCron, isValidSingleField, loadRemoteStyles, matchFlyRoutePattern, nextEnabledIndex, nextModuleIndex, nextSegmentIndex, nextSort, normalizeFlyTheme, normalizeRoles, overlayStack, parseCron, parseStepUpChallenge, parseTags, presenceColorFor, printConsoleSecurityWarning, provideFlyChunkReloadRecovery, provideFlyEmojiPack, provideFlyStandaloneAuth, reloadOnceForChunkError, requestAppLaunch, resolveActiveModule, resolveActiveSection, resolveActiveTab, resolveCellValue, resolveStateIcon, resolveStateMessageKey, restoreFocus, sameTags, sectionHintKey, sectionLayout, sliderTrackGradient, sparklinePath, sparklinePoints, stepUpInterceptor, stepUpReturnKey, trimAgentPayload, trimAgentString, unloadRemoteStyles, utf8ByteLength, validateAgentPayload, warnIfEmbeddedSessionMissing };
21228
21845
  //# sourceMappingURL=flyos-design-system.mjs.map