@flyos/design-system 3.3.0 → 3.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.
@@ -47,7 +47,7 @@ import { CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
47
47
  // tools/publish-library.ps1 at bump time, and asserted by the spec beside this file.
48
48
  // Used only for the diagnostic message; the duplicate-instance detection itself is
49
49
  // version-agnostic, so a stale literal misnames a fork rather than hiding one.
50
- const FLY_DS_VERSION = '3.3.0';
50
+ const FLY_DS_VERSION = '3.4.0';
51
51
  const FLY_DS_REGISTRY_KEY = '__FLY_DS_INSTANCES__';
52
52
  /**
53
53
  * Records this design-system instance on the shared `scope` and returns the
@@ -1680,6 +1680,9 @@ const DS_BASELINE_LOCALES = {
1680
1680
  'ui.nav.selectModule': 'Select a module',
1681
1681
  'ui.nav.sections': 'Sections',
1682
1682
  'ui.nav.switchModule': 'Switch module',
1683
+ 'ui.pager.next': 'Next page',
1684
+ 'ui.pager.page': 'Page {{page}}',
1685
+ 'ui.pager.previous': 'Previous page',
1683
1686
  'ui.pagination.first': 'First page',
1684
1687
  'ui.pagination.label': 'Pagination',
1685
1688
  'ui.pagination.last': 'Last page',
@@ -2070,6 +2073,9 @@ const DS_BASELINE_LOCALES = {
2070
2073
  'ui.nav.selectModule': 'اختر وحدة',
2071
2074
  'ui.nav.sections': 'الأقسام',
2072
2075
  'ui.nav.switchModule': 'تبديل الوحدة',
2076
+ 'ui.pager.next': 'الصفحة التالية',
2077
+ 'ui.pager.page': 'الصفحة {{page}}',
2078
+ 'ui.pager.previous': 'الصفحة السابقة',
2073
2079
  'ui.pagination.first': 'الصفحة الأولى',
2074
2080
  'ui.pagination.label': 'تقسيم الصفحات',
2075
2081
  'ui.pagination.last': 'الصفحة الأخيرة',
@@ -2460,6 +2466,9 @@ const DS_BASELINE_LOCALES = {
2460
2466
  'ui.nav.selectModule': 'Choisir un module',
2461
2467
  'ui.nav.sections': 'Sections',
2462
2468
  'ui.nav.switchModule': 'Changer de module',
2469
+ 'ui.pager.next': 'Page suivante',
2470
+ 'ui.pager.page': 'Page {{page}}',
2471
+ 'ui.pager.previous': 'Page précédente',
2463
2472
  'ui.pagination.first': 'Première page',
2464
2473
  'ui.pagination.label': 'Pagination',
2465
2474
  'ui.pagination.last': 'Dernière page',
@@ -2849,6 +2858,9 @@ const DS_BASELINE_LOCALES = {
2849
2858
  'ui.nav.selectModule': 'ماڈیول منتخب کریں',
2850
2859
  'ui.nav.sections': 'حصے',
2851
2860
  'ui.nav.switchModule': 'ماڈیول تبدیل کریں',
2861
+ 'ui.pager.next': 'اگلا صفحہ',
2862
+ 'ui.pager.page': 'صفحہ {{page}}',
2863
+ 'ui.pager.previous': 'پچھلا صفحہ',
2852
2864
  'ui.pagination.first': 'پہلا صفحہ',
2853
2865
  'ui.pagination.label': 'صفحہ بندی',
2854
2866
  'ui.pagination.last': 'آخری صفحہ',
@@ -4008,12 +4020,12 @@ function connectRemoteLaunch(opts) {
4008
4020
  const FLY_REMOTE_CONTEXT_STORE_KEY = '__flyRemoteContext__';
4009
4021
  const FLY_REMOTE_CONTEXT_EVENT = 'fly:remote-context';
4010
4022
  /** The single cross-bundle store, lazily created on `globalThis`. */
4011
- function store$1() {
4023
+ function store$2() {
4012
4024
  const g = globalThis;
4013
4025
  return (g[FLY_REMOTE_CONTEXT_STORE_KEY] ??= {});
4014
4026
  }
4015
4027
  /** Notify every federated copy of the service that the store changed. */
4016
- function emitSync$1() {
4028
+ function emitSync$2() {
4017
4029
  const g = globalThis;
4018
4030
  try {
4019
4031
  g.dispatchEvent?.(new Event(FLY_REMOTE_CONTEXT_EVENT));
@@ -4070,33 +4082,33 @@ function toMap(s) {
4070
4082
  * dispatch is ever needed.
4071
4083
  */
4072
4084
  class FlyRemoteContextService {
4073
- _byApp = signal(toMap(store$1()), ...(ngDevMode ? [{ debugName: "_byApp" }] : /* istanbul ignore next */ []));
4085
+ _byApp = signal(toMap(store$2()), ...(ngDevMode ? [{ debugName: "_byApp" }] : /* istanbul ignore next */ []));
4074
4086
  /** All currently-published contexts, keyed by app id. Reactive. */
4075
4087
  contexts = this._byApp.asReadonly();
4076
4088
  constructor() {
4077
4089
  // Keep this instance's reactive mirror current when ANOTHER federated copy of
4078
4090
  // the service — or a remote writing the store slot directly — mutates it.
4079
4091
  const g = globalThis;
4080
- g.addEventListener?.(FLY_REMOTE_CONTEXT_EVENT, () => this._byApp.set(toMap(store$1())));
4092
+ g.addEventListener?.(FLY_REMOTE_CONTEXT_EVENT, () => this._byApp.set(toMap(store$2())));
4081
4093
  }
4082
4094
  /** Publish (or replace) the active route context for an app. */
4083
4095
  publish(ctx) {
4084
- store$1()[ctx.appId] = ctx;
4085
- this._byApp.set(toMap(store$1()));
4086
- emitSync$1();
4096
+ store$2()[ctx.appId] = ctx;
4097
+ this._byApp.set(toMap(store$2()));
4098
+ emitSync$2();
4087
4099
  }
4088
4100
  /** Drop an app's context (remote unmounted / window closed). No-op if absent. */
4089
4101
  clear(appId) {
4090
- const s = store$1();
4102
+ const s = store$2();
4091
4103
  if (!(appId in s))
4092
4104
  return;
4093
4105
  delete s[appId];
4094
4106
  this._byApp.set(toMap(s));
4095
- emitSync$1();
4107
+ emitSync$2();
4096
4108
  }
4097
4109
  /** Current context for an app, or `null` when nothing is published. */
4098
4110
  context(appId) {
4099
- return store$1()[appId] ?? null;
4111
+ return store$2()[appId] ?? null;
4100
4112
  }
4101
4113
  /**
4102
4114
  * Resolve a single route param for an app, e.g. `param('circles', 'id')`.
@@ -4104,7 +4116,7 @@ class FlyRemoteContextService {
4104
4116
  * the caller then degrades (the skill asks for the missing id).
4105
4117
  */
4106
4118
  param(appId, path) {
4107
- const value = store$1()[appId]?.params[path];
4119
+ const value = store$2()[appId]?.params[path];
4108
4120
  return value != null && value !== '' ? value : null;
4109
4121
  }
4110
4122
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyRemoteContextService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
@@ -9971,15 +9983,144 @@ class FlyBreadcrumbComponent {
9971
9983
  this.navigate.emit(item);
9972
9984
  }
9973
9985
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyBreadcrumbComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9974
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyBreadcrumbComponent, isStandalone: true, selector: "fly-breadcrumb", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, maxVisible: { classPropertyName: "maxVisible", publicName: "maxVisible", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, hasCurrentPage: { classPropertyName: "hasCurrentPage", publicName: "hasCurrentPage", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { navigate: "navigate" }, host: { properties: { "attr.dir": "i18n.direction()" } }, ngImport: i0, template: "<!--\r\n The CURRENT-PAGE crumb is ALWAYS plain text with aria-current=\"page\" \u2014 never an <a>\r\n or <button>, even when it carries an href. With [hasCurrentPage]=\"false\" the trail\r\n lists ancestors only, so no crumb is the current page and every one stays a link.\r\n The separator is decorative (aria-hidden) and drawn as a CSS mask so it mirrors\r\n under RTL with no directional glyph baked in.\r\n-->\r\n<nav class=\"fly-breadcrumb\" [attr.aria-label]=\"resolvedAriaLabel()\">\r\n <ol class=\"fly-breadcrumb__list\">\r\n @for (entry of renderItems(); track entry.kind === 'item' ? entry.item.id : 'overflow'; let i = $index) {\r\n @if (i > 0) {\r\n <li class=\"fly-breadcrumb__separator\" aria-hidden=\"true\"></li>\r\n }\r\n @if (entry.kind === 'overflow') {\r\n <li class=\"fly-breadcrumb__crumb fly-breadcrumb__crumb--overflow\">\u2026</li>\r\n } @else if (entry.isCurrent) {\r\n <li class=\"fly-breadcrumb__crumb fly-breadcrumb__crumb--current\" aria-current=\"page\">\r\n {{ entry.item.label }}\r\n </li>\r\n } @else {\r\n <li class=\"fly-breadcrumb__crumb\">\r\n @if (entry.item.href) {\r\n <a class=\"fly-breadcrumb__link\" [attr.href]=\"entry.item.href\"\r\n (click)=\"onActivate(entry.item)\">{{ entry.item.label }}</a>\r\n } @else {\r\n <button type=\"button\" class=\"fly-breadcrumb__link fly-breadcrumb__link--button\"\r\n (click)=\"onActivate(entry.item)\">{{ entry.item.label }}</button>\r\n }\r\n </li>\r\n }\r\n }\r\n </ol>\r\n</nav>\r\n", styles: [":host{display:block;min-inline-size:0}.fly-breadcrumb{min-inline-size:0}.fly-breadcrumb__list{display:flex;align-items:center;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none;min-inline-size:0}.fly-breadcrumb__crumb{display:flex;align-items:center;min-inline-size:0;font-size:12.5px;line-height:1.4}.fly-breadcrumb__crumb--current{color:var(--text-color);font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-breadcrumb__crumb--overflow{color:var(--text-color-secondary);-webkit-user-select:none;user-select:none}.fly-breadcrumb__link{display:inline-flex;align-items:center;block-size:26px;padding-inline:8px;margin:0;border:0;border-radius:8px;background:transparent;color:var(--text-color-secondary);font-family:inherit;font-size:12.5px;font-weight:500;line-height:1;text-decoration:none;cursor:pointer;max-inline-size:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:background var(--t-state),color var(--t-state)}.fly-breadcrumb__link:hover{background:var(--w08);color:var(--text-color)}.fly-breadcrumb__link:focus-visible{outline:2px solid var(--primary-color);outline-offset:1px}.fly-breadcrumb__link--button{display:inline-flex}.fly-breadcrumb__separator{flex:0 0 auto;inline-size:7px;block-size:7px;border-block-start:1.5px solid var(--w28);border-inline-end:1.5px solid var(--w28);transform:rotate(45deg)}:host([dir=rtl]) .fly-breadcrumb__separator{transform:rotate(-135deg)}@media(forced-colors:active){.fly-breadcrumb__separator{border-color:CanvasText}.fly-breadcrumb__link:focus-visible{outline-color:Highlight}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9986
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyBreadcrumbComponent, isStandalone: true, selector: "fly-breadcrumb", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, maxVisible: { classPropertyName: "maxVisible", publicName: "maxVisible", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, hasCurrentPage: { classPropertyName: "hasCurrentPage", publicName: "hasCurrentPage", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { navigate: "navigate" }, host: { properties: { "attr.dir": "i18n.direction()" } }, ngImport: i0, template: "<!--\r\n The CURRENT-PAGE crumb is ALWAYS plain text with aria-current=\"page\" \u2014 never an <a>\r\n or <button>, even when it carries an href. With [hasCurrentPage]=\"false\" the trail\r\n lists ancestors only, so no crumb is the current page and every one stays a link.\r\n The separator is decorative (aria-hidden) and drawn as a CSS mask so it mirrors\r\n under RTL with no directional glyph baked in.\r\n-->\r\n<nav class=\"fly-breadcrumb\" [attr.aria-label]=\"resolvedAriaLabel()\">\r\n @for (entry of renderItems(); track entry.kind === 'item' ? entry.item.id : 'overflow'; let i = $index) {\r\n @if (i > 0) {\r\n <svg class=\"fly-breadcrumb__separator\" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\">\r\n <path d=\"M9 5l7 7-7 7\" />\r\n </svg>\r\n }\r\n @if (entry.kind === 'overflow') {\r\n <span class=\"fly-breadcrumb__crumb fly-breadcrumb__crumb--overflow\">\u2026</span>\r\n } @else if (entry.isCurrent) {\r\n <span class=\"fly-breadcrumb__crumb fly-breadcrumb__crumb--current\" aria-current=\"page\">\r\n @if (entry.item.iconPath; as d) {\r\n <svg class=\"fly-breadcrumb__icon\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\"><path [attr.d]=\"d\" /></svg>\r\n }\r\n {{ entry.item.label }}\r\n </span>\r\n } @else {\r\n <span class=\"fly-breadcrumb__crumb\">\r\n @if (entry.item.href) {\r\n <a class=\"fly-breadcrumb__link\" [attr.href]=\"entry.item.href\"\r\n (click)=\"onActivate(entry.item)\">\r\n @if (entry.item.iconPath; as d) {\r\n <svg class=\"fly-breadcrumb__icon\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\"><path [attr.d]=\"d\" /></svg>\r\n }\r\n {{ entry.item.label }}\r\n </a>\r\n } @else {\r\n <button type=\"button\" class=\"fly-breadcrumb__link fly-breadcrumb__link--button\"\r\n (click)=\"onActivate(entry.item)\">\r\n @if (entry.item.iconPath; as d) {\r\n <svg class=\"fly-breadcrumb__icon\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\"><path [attr.d]=\"d\" /></svg>\r\n }\r\n {{ entry.item.label }}\r\n </button>\r\n }\r\n </span>\r\n }\r\n }\r\n</nav>\r\n", styles: [":host{display:block;min-inline-size:0}.fly-breadcrumb{display:flex;align-items:center;gap:2px;min-inline-size:0}.fly-breadcrumb__crumb{display:flex;align-items:center;flex:none;min-inline-size:0;font-size:12.5px;line-height:1.4}.fly-breadcrumb__crumb--current{flex:1 1 auto;min-inline-size:0;gap:6px;block-size:26px;padding-inline:4px;font-weight:600;letter-spacing:-.006em;color:var(--text-color);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host([dir=rtl]) .fly-breadcrumb__crumb--current{letter-spacing:normal}.fly-breadcrumb__crumb--overflow{color:var(--text-color-secondary);-webkit-user-select:none;user-select:none}.fly-breadcrumb__link{display:inline-flex;align-items:center;gap:6px;block-size:26px;padding-inline:8px;margin:0;border:0;border-radius:8px;background:transparent;color:var(--text-color-secondary);font-family:inherit;font-size:12.5px;font-weight:500;line-height:1;text-decoration:none;cursor:pointer;max-inline-size:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:background var(--nova-duration-hover) var(--nova-ease-micro),color var(--nova-duration-hover) var(--nova-ease-micro)}.fly-breadcrumb__link:hover{background:var(--w08);color:var(--text-color)}.fly-breadcrumb__link:focus-visible{outline:2px solid var(--primary-color);outline-offset:1px}.fly-breadcrumb__link--button{display:inline-flex}.fly-breadcrumb__icon{flex:none}.fly-breadcrumb__separator{flex:none;color:var(--w28)}:host([dir=rtl]) .fly-breadcrumb__separator{transform:scaleX(-1)}@media(forced-colors:active){.fly-breadcrumb__separator{color:CanvasText}.fly-breadcrumb__link:focus-visible{outline-color:Highlight}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9975
9987
  }
9976
9988
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyBreadcrumbComponent, decorators: [{
9977
9989
  type: Component,
9978
9990
  args: [{ selector: 'fly-breadcrumb', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, host: {
9979
9991
  '[attr.dir]': 'i18n.direction()',
9980
- }, template: "<!--\r\n The CURRENT-PAGE crumb is ALWAYS plain text with aria-current=\"page\" \u2014 never an <a>\r\n or <button>, even when it carries an href. With [hasCurrentPage]=\"false\" the trail\r\n lists ancestors only, so no crumb is the current page and every one stays a link.\r\n The separator is decorative (aria-hidden) and drawn as a CSS mask so it mirrors\r\n under RTL with no directional glyph baked in.\r\n-->\r\n<nav class=\"fly-breadcrumb\" [attr.aria-label]=\"resolvedAriaLabel()\">\r\n <ol class=\"fly-breadcrumb__list\">\r\n @for (entry of renderItems(); track entry.kind === 'item' ? entry.item.id : 'overflow'; let i = $index) {\r\n @if (i > 0) {\r\n <li class=\"fly-breadcrumb__separator\" aria-hidden=\"true\"></li>\r\n }\r\n @if (entry.kind === 'overflow') {\r\n <li class=\"fly-breadcrumb__crumb fly-breadcrumb__crumb--overflow\">\u2026</li>\r\n } @else if (entry.isCurrent) {\r\n <li class=\"fly-breadcrumb__crumb fly-breadcrumb__crumb--current\" aria-current=\"page\">\r\n {{ entry.item.label }}\r\n </li>\r\n } @else {\r\n <li class=\"fly-breadcrumb__crumb\">\r\n @if (entry.item.href) {\r\n <a class=\"fly-breadcrumb__link\" [attr.href]=\"entry.item.href\"\r\n (click)=\"onActivate(entry.item)\">{{ entry.item.label }}</a>\r\n } @else {\r\n <button type=\"button\" class=\"fly-breadcrumb__link fly-breadcrumb__link--button\"\r\n (click)=\"onActivate(entry.item)\">{{ entry.item.label }}</button>\r\n }\r\n </li>\r\n }\r\n }\r\n </ol>\r\n</nav>\r\n", styles: [":host{display:block;min-inline-size:0}.fly-breadcrumb{min-inline-size:0}.fly-breadcrumb__list{display:flex;align-items:center;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none;min-inline-size:0}.fly-breadcrumb__crumb{display:flex;align-items:center;min-inline-size:0;font-size:12.5px;line-height:1.4}.fly-breadcrumb__crumb--current{color:var(--text-color);font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-breadcrumb__crumb--overflow{color:var(--text-color-secondary);-webkit-user-select:none;user-select:none}.fly-breadcrumb__link{display:inline-flex;align-items:center;block-size:26px;padding-inline:8px;margin:0;border:0;border-radius:8px;background:transparent;color:var(--text-color-secondary);font-family:inherit;font-size:12.5px;font-weight:500;line-height:1;text-decoration:none;cursor:pointer;max-inline-size:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:background var(--t-state),color var(--t-state)}.fly-breadcrumb__link:hover{background:var(--w08);color:var(--text-color)}.fly-breadcrumb__link:focus-visible{outline:2px solid var(--primary-color);outline-offset:1px}.fly-breadcrumb__link--button{display:inline-flex}.fly-breadcrumb__separator{flex:0 0 auto;inline-size:7px;block-size:7px;border-block-start:1.5px solid var(--w28);border-inline-end:1.5px solid var(--w28);transform:rotate(45deg)}:host([dir=rtl]) .fly-breadcrumb__separator{transform:rotate(-135deg)}@media(forced-colors:active){.fly-breadcrumb__separator{border-color:CanvasText}.fly-breadcrumb__link:focus-visible{outline-color:Highlight}}\n"] }]
9992
+ }, template: "<!--\r\n The CURRENT-PAGE crumb is ALWAYS plain text with aria-current=\"page\" \u2014 never an <a>\r\n or <button>, even when it carries an href. With [hasCurrentPage]=\"false\" the trail\r\n lists ancestors only, so no crumb is the current page and every one stays a link.\r\n The separator is decorative (aria-hidden) and drawn as a CSS mask so it mirrors\r\n under RTL with no directional glyph baked in.\r\n-->\r\n<nav class=\"fly-breadcrumb\" [attr.aria-label]=\"resolvedAriaLabel()\">\r\n @for (entry of renderItems(); track entry.kind === 'item' ? entry.item.id : 'overflow'; let i = $index) {\r\n @if (i > 0) {\r\n <svg class=\"fly-breadcrumb__separator\" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\">\r\n <path d=\"M9 5l7 7-7 7\" />\r\n </svg>\r\n }\r\n @if (entry.kind === 'overflow') {\r\n <span class=\"fly-breadcrumb__crumb fly-breadcrumb__crumb--overflow\">\u2026</span>\r\n } @else if (entry.isCurrent) {\r\n <span class=\"fly-breadcrumb__crumb fly-breadcrumb__crumb--current\" aria-current=\"page\">\r\n @if (entry.item.iconPath; as d) {\r\n <svg class=\"fly-breadcrumb__icon\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\"><path [attr.d]=\"d\" /></svg>\r\n }\r\n {{ entry.item.label }}\r\n </span>\r\n } @else {\r\n <span class=\"fly-breadcrumb__crumb\">\r\n @if (entry.item.href) {\r\n <a class=\"fly-breadcrumb__link\" [attr.href]=\"entry.item.href\"\r\n (click)=\"onActivate(entry.item)\">\r\n @if (entry.item.iconPath; as d) {\r\n <svg class=\"fly-breadcrumb__icon\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\"><path [attr.d]=\"d\" /></svg>\r\n }\r\n {{ entry.item.label }}\r\n </a>\r\n } @else {\r\n <button type=\"button\" class=\"fly-breadcrumb__link fly-breadcrumb__link--button\"\r\n (click)=\"onActivate(entry.item)\">\r\n @if (entry.item.iconPath; as d) {\r\n <svg class=\"fly-breadcrumb__icon\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\"><path [attr.d]=\"d\" /></svg>\r\n }\r\n {{ entry.item.label }}\r\n </button>\r\n }\r\n </span>\r\n }\r\n }\r\n</nav>\r\n", styles: [":host{display:block;min-inline-size:0}.fly-breadcrumb{display:flex;align-items:center;gap:2px;min-inline-size:0}.fly-breadcrumb__crumb{display:flex;align-items:center;flex:none;min-inline-size:0;font-size:12.5px;line-height:1.4}.fly-breadcrumb__crumb--current{flex:1 1 auto;min-inline-size:0;gap:6px;block-size:26px;padding-inline:4px;font-weight:600;letter-spacing:-.006em;color:var(--text-color);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host([dir=rtl]) .fly-breadcrumb__crumb--current{letter-spacing:normal}.fly-breadcrumb__crumb--overflow{color:var(--text-color-secondary);-webkit-user-select:none;user-select:none}.fly-breadcrumb__link{display:inline-flex;align-items:center;gap:6px;block-size:26px;padding-inline:8px;margin:0;border:0;border-radius:8px;background:transparent;color:var(--text-color-secondary);font-family:inherit;font-size:12.5px;font-weight:500;line-height:1;text-decoration:none;cursor:pointer;max-inline-size:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:background var(--nova-duration-hover) var(--nova-ease-micro),color var(--nova-duration-hover) var(--nova-ease-micro)}.fly-breadcrumb__link:hover{background:var(--w08);color:var(--text-color)}.fly-breadcrumb__link:focus-visible{outline:2px solid var(--primary-color);outline-offset:1px}.fly-breadcrumb__link--button{display:inline-flex}.fly-breadcrumb__icon{flex:none}.fly-breadcrumb__separator{flex:none;color:var(--w28)}:host([dir=rtl]) .fly-breadcrumb__separator{transform:scaleX(-1)}@media(forced-colors:active){.fly-breadcrumb__separator{color:CanvasText}.fly-breadcrumb__link:focus-visible{outline-color:Highlight}}\n"] }]
9981
9993
  }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], maxVisible: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxVisible", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], hasCurrentPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasCurrentPage", required: false }] }], navigate: [{ type: i0.Output, args: ["navigate"] }] } });
9982
9994
 
9995
+ /** Cross-bundle store key + sync event — same shape as `FLY_MAGIC_BAR_STORE_KEY`. */
9996
+ const FLY_WINDOW_BREADCRUMBS_STORE_KEY = '__flyWindowBreadcrumbs__';
9997
+ const FLY_WINDOW_BREADCRUMBS_EVENT = 'fly:window-breadcrumbs';
9998
+ function store$1() {
9999
+ const g = globalThis;
10000
+ return (g[FLY_WINDOW_BREADCRUMBS_STORE_KEY] ??= { trails: {}, revision: 0 });
10001
+ }
10002
+ function emitSync$1() {
10003
+ const g = globalThis;
10004
+ try {
10005
+ g.dispatchEvent?.(new Event(FLY_WINDOW_BREADCRUMBS_EVENT));
10006
+ }
10007
+ catch {
10008
+ // Non-DOM realm (SSR / a unit env without `Event`) — see the identical note on
10009
+ // `MagicBarRegistry.emitSync`. The instance that mutated the store is
10010
+ // unaffected; any OTHER instance in such a realm stays stale until its own
10011
+ // next mutation calls `sync()` directly.
10012
+ }
10013
+ }
10014
+ /**
10015
+ * Where an app publishes the breadcrumb trail its window's header strip renders.
10016
+ *
10017
+ * The design puts a trail inside the window
10018
+ * (`UX/FlyOS Desktop-app/MainContent.dc.html`, `nav[aria-label="Breadcrumb"]`), and
10019
+ * the window chrome (`WindowComponent`) renders whatever this registry holds for
10020
+ * its window id, falling back to the plain window title when nothing has published.
10021
+ *
10022
+ * ## Why the state lives on `globalThis`
10023
+ *
10024
+ * Same reasoning as {@link MagicBarRegistry} (`services/magic-bar/`), and the same
10025
+ * direction of travel (remote → shell chrome). This service is `providedIn: 'root'`,
10026
+ * but a root instance is not *reliably* shared across the federation boundary: the
10027
+ * shell builds the design system from workspace source while remotes consume the
10028
+ * published package, and Native Federation collapses those into one runtime
10029
+ * instance only when version negotiation succeeds — a split there is invisible at
10030
+ * build time and would otherwise present as a window whose breadcrumb silently
10031
+ * never appears, for federated apps only. Keeping the state on the one substrate
10032
+ * every bundle shares makes this store fork-proof: whichever copy of this service
10033
+ * publishes, every copy's `trail()` agrees. Each instance mirrors the store into a
10034
+ * local signal and re-reads on the sync event, so `WindowComponent`'s template —
10035
+ * which reads `trail()` inside its own reactive context — repaints whichever
10036
+ * bundle actually published.
10037
+ *
10038
+ * Simpler than `MagicBarRegistry` on purpose: a breadcrumb trail is addressed
10039
+ * directly by the window id its publisher already has (via `WINDOW_DATA`), so
10040
+ * there is no owner-vs-active-window arbitration, no per-action indexing and no
10041
+ * publish-fingerprint dedup to carry — `publish()` just replaces the entry.
10042
+ *
10043
+ * ```ts
10044
+ * const win = inject(WINDOW_DATA);
10045
+ * const crumbs = inject(WindowBreadcrumbsRegistry);
10046
+ * crumbs.publish(win.id, [
10047
+ * { id: 'root', label: circleName, navigate: () => this.showList() },
10048
+ * { id: 'trend', label: trend.title },
10049
+ * ]);
10050
+ * ```
10051
+ */
10052
+ class WindowBreadcrumbsRegistry {
10053
+ _trails = signal(new Map(), ...(ngDevMode ? [{ debugName: "_trails" }] : /* istanbul ignore next */ []));
10054
+ _syncedStore = null;
10055
+ _syncedRevision = -1;
10056
+ /** Every published trail, keyed by window id. Reactive — read it inside a template/computed/effect. */
10057
+ all = this._trails.asReadonly();
10058
+ constructor() {
10059
+ const g = globalThis;
10060
+ g.addEventListener?.(FLY_WINDOW_BREADCRUMBS_EVENT, () => this.sync());
10061
+ this.sync();
10062
+ }
10063
+ /**
10064
+ * This window's published trail, or an empty array.
10065
+ *
10066
+ * A plain method over a signal read, not a per-id `computed`: it is called from
10067
+ * the window template, where the signal read is tracked whatever the call depth,
10068
+ * and a `computed` cache keyed by a mutable window id would outlive its window.
10069
+ */
10070
+ trail(windowId) {
10071
+ return this._trails().get(windowId) ?? EMPTY_TRAIL;
10072
+ }
10073
+ /**
10074
+ * Replace a window's trail. Publishing an empty array is how an app goes back to
10075
+ * showing the plain window title — it is a state, not a clear.
10076
+ */
10077
+ publish(windowId, crumbs) {
10078
+ const s = store$1();
10079
+ s.trails = { ...s.trails, [windowId]: [...crumbs] };
10080
+ s.revision++;
10081
+ this.sync();
10082
+ emitSync$1();
10083
+ }
10084
+ /** Drop a window's trail entirely. The window chrome calls this when it is destroyed. */
10085
+ clear(windowId) {
10086
+ const s = store$1();
10087
+ if (!(windowId in s.trails))
10088
+ return;
10089
+ const trails = { ...s.trails };
10090
+ delete trails[windowId];
10091
+ s.trails = trails;
10092
+ s.revision++;
10093
+ this.sync();
10094
+ emitSync$1();
10095
+ }
10096
+ /**
10097
+ * Re-read the shared store. Cheap no-op unless a real mutation landed.
10098
+ *
10099
+ * Keys on store IDENTITY as well as revision — see the identical note on
10100
+ * `MagicBarRegistry.sync`: a revision counter alone cannot distinguish "nothing
10101
+ * changed" from "the `globalThis` slot now holds a brand-new store that happens
10102
+ * to be at the same revision" (every spec's `beforeEach` replaces the store).
10103
+ */
10104
+ sync() {
10105
+ const s = store$1();
10106
+ if (s === this._syncedStore && s.revision === this._syncedRevision)
10107
+ return;
10108
+ this._syncedStore = s;
10109
+ this._syncedRevision = s.revision;
10110
+ this._trails.set(new Map(Object.entries(s.trails)));
10111
+ }
10112
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: WindowBreadcrumbsRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
10113
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: WindowBreadcrumbsRegistry, providedIn: 'root' });
10114
+ }
10115
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: WindowBreadcrumbsRegistry, decorators: [{
10116
+ type: Injectable,
10117
+ args: [{ providedIn: 'root' }]
10118
+ }], ctorParameters: () => [] });
10119
+ /** Shared empty result — a fresh `[]` per read would re-render every window on every pass. */
10120
+ const EMPTY_TRAIL = Object.freeze([]);
10121
+
10122
+ /** Public window-breadcrumbs surface — see `window-breadcrumbs-registry.service.ts`. */
10123
+
9983
10124
  /**
9984
10125
  * Headless keyboard-navigation logic — roving focus for menus, radiogroups, and
9985
10126
  * tablists.
@@ -12768,6 +12909,53 @@ function resolveLinks(deps, renderableIds) {
12768
12909
  }
12769
12910
  return out;
12770
12911
  }
12912
+ /** Straight run (px) a dependency elbow travels out of a bar edge before it turns. */
12913
+ const LINK_STUB = 12;
12914
+ /**
12915
+ * Relationship kind implied by the edge a gesture left and the edge it landed on. The name is
12916
+ * literally `<predecessor endpoint><successor endpoint>`, so this is a lookup, not a rule.
12917
+ */
12918
+ function linkTypeFor(from, to) {
12919
+ if (from === 'finish')
12920
+ return to === 'start' ? 'FS' : 'FF';
12921
+ return to === 'start' ? 'SS' : 'SF';
12922
+ }
12923
+ /**
12924
+ * Route a dependency elbow for any of the four relationship kinds (render space).
12925
+ *
12926
+ * The kind names the two endpoints, so it decides both the anchor x's and the direction the
12927
+ * arrow travels: leaving a *finish* edge moves forward in time, leaving a *start* edge moves
12928
+ * backward; entering a *start* edge approaches from behind, entering a *finish* edge from
12929
+ * ahead. `fwd` folds RTL in, so none of that branches on direction.
12930
+ *
12931
+ * One turn suffices when the entry stub lies ahead of the exit stub in the approach
12932
+ * direction. When it does not — overlapping or reversed bars, which SS/FF/SF hit constantly —
12933
+ * a straight drop would cut back through the bars, so the elbow detours via the gap between
12934
+ * the two rows instead.
12935
+ */
12936
+ function routeLink(from, to, type, rtl, rowHeight) {
12937
+ const fwd = rtl ? -1 : 1;
12938
+ const fromFinish = type === 'FS' || type === 'FF';
12939
+ const toStart = type === 'FS' || type === 'SS';
12940
+ const x1 = from.shape === 'milestone' ? from.pointX : fromFinish ? from.endX : from.startX;
12941
+ const x2 = to.shape === 'milestone' ? to.pointX : toStart ? to.startX : to.endX;
12942
+ const y1 = from.midY;
12943
+ const y2 = to.midY;
12944
+ const d1 = (fromFinish ? 1 : -1) * fwd; // exit direction
12945
+ const d2 = (toStart ? 1 : -1) * fwd; // approach direction
12946
+ const exit = x1 + LINK_STUB * d1;
12947
+ const entry = x2 - LINK_STUB * d2;
12948
+ if ((entry - exit) * d2 >= 0) {
12949
+ return { path: `M ${x1} ${y1} H ${exit} V ${y2} H ${x2}`, badgeX: exit, badgeY: (y1 + y2) / 2 };
12950
+ }
12951
+ // Distinct rows always differ in y; the guard only keeps a degenerate input off the bar.
12952
+ const yMid = y1 === y2 ? y1 - rowHeight / 2 : (y1 + y2) / 2;
12953
+ return {
12954
+ path: `M ${x1} ${y1} H ${exit} V ${yMid} H ${entry} V ${y2} H ${x2}`,
12955
+ badgeX: entry,
12956
+ badgeY: yMid,
12957
+ };
12958
+ }
12771
12959
  // ── RTL coordinate mapping ───────────────────────────────────────────────────
12772
12960
  /** Mirror a logical x into render space. LTR is identity; RTL reflects about the canvas width. */
12773
12961
  function mapX(logicalX, innerWidth, rtl) {
@@ -12868,6 +13056,257 @@ function weekendBands(domain, zoom, pxPerDay) {
12868
13056
  return bands;
12869
13057
  }
12870
13058
 
13059
+ /** Label-pane width bounds (px) the splitter clamps to. */
13060
+ const GANTT_LABEL_W_MIN = 140;
13061
+ const GANTT_LABEL_W_MAX = 720;
13062
+ /** Keyboard resize steps (plain arrow / with Shift). */
13063
+ const LABEL_STEP = 16;
13064
+ const LABEL_STEP_COARSE = 64;
13065
+ /** Clamp any candidate label-pane width into the splitter's range. */
13066
+ function clampGanttLabelWidth(px) {
13067
+ return Math.max(GANTT_LABEL_W_MIN, Math.min(GANTT_LABEL_W_MAX, Math.round(px)));
13068
+ }
13069
+ /**
13070
+ * The Gantt's label-pane divider: width state plus the pointer and keyboard resize gestures.
13071
+ *
13072
+ * ## Why this is not in the component
13073
+ * `fly-gantt` is a large component by nature — it owns a two-pane layout, a time scale, four row
13074
+ * shapes, drag/resize/link gestures and dependency routing. The pane divider is the one region
13075
+ * with *no* coupling to any of that: it needs a direction, a fallback width and somewhere to
13076
+ * report a committed value, and nothing else. Keeping it here means the component's remaining
13077
+ * bulk is all chart, and the divider's RTL rules — which are the subtle part — are stated in one
13078
+ * place and unit-testable without rendering an SVG.
13079
+ *
13080
+ * ## The RTL rule, once
13081
+ * The label pane sits on the inline-START side in both directions, so "drag outward = wider"
13082
+ * flips physical sign with direction, and the arrow key that widens is the one pointing AWAY
13083
+ * from the pane (`ArrowRight` in LTR, `ArrowLeft` in RTL). Both facts are encoded here rather
13084
+ * than at each call site, which is what stops one of the two gestures being mirrored and the
13085
+ * other not — the failure this consolidation exists to prevent.
13086
+ */
13087
+ class GanttLabelPane {
13088
+ deps;
13089
+ /** True for the duration of a pointer drag — the host paints a resizing affordance. */
13090
+ resizing = signal(false, ...(ngDevMode ? [{ debugName: "resizing" }] : /* istanbul ignore next */ []));
13091
+ /** The user's dragged width, or `null` while the consumer's input is in charge. */
13092
+ _override = signal(null, ...(ngDevMode ? [{ debugName: "_override" }] : /* istanbul ignore next */ []));
13093
+ /** The width to render: the user's drag when present, else the clamped input. */
13094
+ width = computed(() => clampGanttLabelWidth(this._override() ?? this.deps.inputWidth()), ...(ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []));
13095
+ constructor(deps) {
13096
+ this.deps = deps;
13097
+ }
13098
+ /**
13099
+ * Pointer-drag the divider. Forward-in-time is `+x` LTR and `-x` RTL, and the label pane sits
13100
+ * on the inline-start side in both, so the same sign flip that mirrors the timeline also
13101
+ * mirrors "drag outward = wider".
13102
+ *
13103
+ * Listeners go on `document`, not the divider: a fast drag outruns the 8px-wide handle, and a
13104
+ * pointer that leaves it would otherwise strand the gesture mid-resize.
13105
+ */
13106
+ onPointerDown(ev) {
13107
+ if (!this.deps.resizable() || ev.button !== 0)
13108
+ return;
13109
+ ev.preventDefault();
13110
+ ev.stopPropagation();
13111
+ const startClientX = ev.clientX;
13112
+ const startWidth = this.width();
13113
+ const dirSign = this.deps.rtl() ? -1 : 1;
13114
+ this.resizing.set(true);
13115
+ const onMove = (e) => {
13116
+ this._override.set(clampGanttLabelWidth(startWidth + (e.clientX - startClientX) * dirSign));
13117
+ };
13118
+ const onUp = () => {
13119
+ document.removeEventListener('pointermove', onMove);
13120
+ document.removeEventListener('pointerup', onUp);
13121
+ this.resizing.set(false);
13122
+ const final = this.width();
13123
+ // Only a width that actually MOVED is committed: a plain click on the divider must not
13124
+ // emit, or a consumer persisting the value would write on every stray press.
13125
+ if (final !== startWidth)
13126
+ this.deps.commit(final);
13127
+ };
13128
+ document.addEventListener('pointermove', onMove);
13129
+ document.addEventListener('pointerup', onUp);
13130
+ this.deps.destroyRef.onDestroy(() => {
13131
+ document.removeEventListener('pointermove', onMove);
13132
+ document.removeEventListener('pointerup', onUp);
13133
+ });
13134
+ }
13135
+ /**
13136
+ * Keyboard resize on the focused divider. ArrowRight/Left grow/shrink by the *inline* meaning
13137
+ * of the key, so under RTL the arrow that points away from the pane is still the one that
13138
+ * widens it. `Home`/`End` jump to the bounds, Enter/Escape restore the input width.
13139
+ *
13140
+ * Typed `Event` because Angular's strict template checker types a `(keydown)` `$event` as
13141
+ * `Event`; the cast is done here once rather than at the binding.
13142
+ */
13143
+ onKeydown(ev) {
13144
+ if (!this.deps.resizable())
13145
+ return;
13146
+ const key = ev.key;
13147
+ const coarse = ev.shiftKey;
13148
+ const step = coarse ? LABEL_STEP_COARSE : LABEL_STEP;
13149
+ const rtl = this.deps.rtl();
13150
+ const grow = rtl ? 'ArrowLeft' : 'ArrowRight';
13151
+ const shrink = rtl ? 'ArrowRight' : 'ArrowLeft';
13152
+ let next;
13153
+ if (key === grow)
13154
+ next = this.width() + step;
13155
+ else if (key === shrink)
13156
+ next = this.width() - step;
13157
+ else if (key === 'Home')
13158
+ next = GANTT_LABEL_W_MIN;
13159
+ else if (key === 'End')
13160
+ next = GANTT_LABEL_W_MAX;
13161
+ else if (key === 'Enter' || key === 'Escape') {
13162
+ // Stop the grid-level Enter handler from also activating the selected row.
13163
+ ev.preventDefault();
13164
+ ev.stopPropagation();
13165
+ this.reset();
13166
+ return;
13167
+ }
13168
+ else
13169
+ return;
13170
+ ev.preventDefault();
13171
+ ev.stopPropagation();
13172
+ const clamped = clampGanttLabelWidth(next);
13173
+ // Already at a bound: swallow the key (it was ours) but emit nothing.
13174
+ if (clamped === this.width())
13175
+ return;
13176
+ this._override.set(clamped);
13177
+ this.deps.commit(clamped);
13178
+ }
13179
+ /** Drop the user's dragged width and fall back to the consumer's input width. */
13180
+ reset() {
13181
+ if (this._override() === null)
13182
+ return;
13183
+ this._override.set(null);
13184
+ this.deps.commit(this.width());
13185
+ }
13186
+ }
13187
+
13188
+ /**
13189
+ * Drawing a dependency by dragging from one end of a bar to another — the gesture, the drop-target
13190
+ * resolution and the resulting relationship type.
13191
+ *
13192
+ * ## Why this is not in the component
13193
+ * `fly-gantt` already owns a two-pane layout, a time scale, four row shapes and bar move/resize.
13194
+ * This is a self-contained second gesture with one output, and its interesting part is a *rule*
13195
+ * rather than any rendering: which end you START on plus which end you DROP nearest decides which
13196
+ * of the four MS-Project relationships you get. Stating that rule beside the hit-testing that
13197
+ * feeds it — rather than in the middle of a 700-line component — is what makes it reviewable.
13198
+ *
13199
+ * ## The selection is here too, and deliberately
13200
+ * `selectedKey` is the link the user has clicked, which is what the delete affordance and the
13201
+ * keyboard's Delete both act on. It lives with the gesture because both are "the user is
13202
+ * manipulating dependencies", and keeping them together stops a click-selection surviving a drag
13203
+ * that replaced the very link it pointed at.
13204
+ */
13205
+ class GanttLinkGestures {
13206
+ deps;
13207
+ /** The in-flight drag, or `null`. */
13208
+ gesture = signal(null, ...(ngDevMode ? [{ debugName: "gesture" }] : /* istanbul ignore next */ []));
13209
+ /** The clicked link's key, or `null`. Drives the delete affordance and the Delete key. */
13210
+ selectedKey = signal(null, ...(ngDevMode ? [{ debugName: "selectedKey" }] : /* istanbul ignore next */ []));
13211
+ /** Rubber-band path for the in-flight gesture (render space), or `null` when idle. */
13212
+ path = computed(() => {
13213
+ const g = this.gesture();
13214
+ return g ? `M ${g.x0} ${g.y0} L ${g.x1} ${g.y1}` : null;
13215
+ }, ...(ngDevMode ? [{ debugName: "path" }] : /* istanbul ignore next */ []));
13216
+ constructor(deps) {
13217
+ this.deps = deps;
13218
+ }
13219
+ /** Clear any link selection — the host calls this when the user selects a row instead. */
13220
+ clearSelection() {
13221
+ this.selectedKey.set(null);
13222
+ }
13223
+ /** Toggle the clicked link's selection. */
13224
+ toggleSelection(key) {
13225
+ this.selectedKey.set(this.selectedKey() === key ? null : key);
13226
+ }
13227
+ /**
13228
+ * Start a link gesture from one end of a bar. Which end it started on, plus which end of the
13229
+ * target row it is dropped nearest to, is what selects the dependency type — so all four
13230
+ * MS-Project relationships are reachable with the same single drag.
13231
+ *
13232
+ * Listeners go on `document`, not the connector: the connector is an 8px dot and any real drag
13233
+ * leaves it immediately.
13234
+ */
13235
+ start(ev, row, anchor) {
13236
+ if (this.deps.readonly() || row.flat.row.readonly || ev.button !== 0)
13237
+ return;
13238
+ ev.preventDefault();
13239
+ ev.stopPropagation();
13240
+ const local = this.toBodyPoint(ev.clientX, ev.clientY);
13241
+ const x0 = row.shape === 'milestone' ? row.pointX : anchor === 'finish' ? row.endX : row.startX;
13242
+ this.gesture.set({
13243
+ fromId: row.flat.row.id,
13244
+ anchor,
13245
+ x0,
13246
+ y0: row.midY,
13247
+ x1: local.x,
13248
+ y1: local.y,
13249
+ });
13250
+ const onMove = (e) => {
13251
+ const p = this.toBodyPoint(e.clientX, e.clientY);
13252
+ const g = this.gesture();
13253
+ if (g)
13254
+ this.gesture.set({ ...g, x1: p.x, y1: p.y });
13255
+ };
13256
+ const onUp = (e) => {
13257
+ document.removeEventListener('pointermove', onMove);
13258
+ document.removeEventListener('pointerup', onUp);
13259
+ const g = this.gesture();
13260
+ this.gesture.set(null);
13261
+ if (!g)
13262
+ return;
13263
+ const target = this.dropTargetAt(e.clientX, e.clientY);
13264
+ // A row cannot depend on itself; a drop on empty space is simply abandoned.
13265
+ if (target && target.id !== g.fromId) {
13266
+ this.deps.createLink({
13267
+ fromId: g.fromId,
13268
+ toId: target.id,
13269
+ type: linkTypeFor(g.anchor, target.anchor),
13270
+ });
13271
+ }
13272
+ };
13273
+ document.addEventListener('pointermove', onMove);
13274
+ document.addEventListener('pointerup', onUp);
13275
+ this.deps.destroyRef.onDestroy(() => {
13276
+ document.removeEventListener('pointermove', onMove);
13277
+ document.removeEventListener('pointerup', onUp);
13278
+ });
13279
+ }
13280
+ /** Convert client coordinates to a point inside the body SVG (render space). */
13281
+ toBodyPoint(clientX, clientY) {
13282
+ const svg = this.deps.bodySvg();
13283
+ if (!svg)
13284
+ return { x: 0, y: 0 };
13285
+ const rect = svg.getBoundingClientRect();
13286
+ return { x: clientX - rect.left, y: clientY - rect.top };
13287
+ }
13288
+ /**
13289
+ * The row a link gesture was dropped on **and which of its ends** the drop landed nearest —
13290
+ * the second half of the type derivation. A milestone has no width, so both of its ends are the
13291
+ * same point and it is reported as `start`, giving the FS/SS pair anyone actually wants.
13292
+ */
13293
+ dropTargetAt(clientX, clientY) {
13294
+ const el = document.elementFromPoint(clientX, clientY);
13295
+ const holder = el?.closest('[data-row-id]');
13296
+ const id = holder?.getAttribute('data-row-id');
13297
+ if (!id)
13298
+ return null;
13299
+ const row = this.deps.rows().find((r) => r.flat.row.id === id);
13300
+ if (!row || row.shape === 'milestone' || row.shape === 'empty')
13301
+ return { id, anchor: 'start' };
13302
+ const x = this.toBodyPoint(clientX, clientY).x;
13303
+ return {
13304
+ id,
13305
+ anchor: Math.abs(x - row.endX) < Math.abs(x - row.startX) ? 'finish' : 'start',
13306
+ };
13307
+ }
13308
+ }
13309
+
12871
13310
  /** Process-wide counter so each instance owns a unique arrowhead `<marker>` id. */
12872
13311
  let _flyGanttUid = 0;
12873
13312
  /** Header band heights (two-tier: coarse period over fine period). */
@@ -12878,27 +13317,6 @@ const HEADER_H = HEADER_UPPER_H + HEADER_LOWER_H;
12878
13317
  const BAR_V_PAD = 6;
12879
13318
  /** Edge-handle hit width (px) for resize + the link connector radius. */
12880
13319
  const HANDLE_W = 8;
12881
- /** Label-pane width bounds (px) the splitter clamps to. */
12882
- const MIN_LABEL_W = 140;
12883
- const MAX_LABEL_W = 720;
12884
- /** Keyboard resize steps (plain arrow / with Shift). */
12885
- const LABEL_STEP = 16;
12886
- const LABEL_STEP_COARSE = 64;
12887
- /** Straight run (px) a dependency elbow travels out of a bar edge before it turns. */
12888
- const LINK_STUB = 12;
12889
- /** Clamp any candidate label-pane width into the splitter's range. */
12890
- function clampLabelWidth(px) {
12891
- return Math.max(MIN_LABEL_W, Math.min(MAX_LABEL_W, Math.round(px)));
12892
- }
12893
- /**
12894
- * Relationship kind implied by the edge a gesture left and the edge it landed on. The name is
12895
- * literally `<predecessor endpoint><successor endpoint>`, so this is a lookup, not a rule.
12896
- */
12897
- function linkTypeFor(from, to) {
12898
- if (from === 'finish')
12899
- return to === 'start' ? 'FS' : 'FF';
12900
- return to === 'start' ? 'SS' : 'SF';
12901
- }
12902
13320
  /**
12903
13321
  * **`fly-gantt`** — the design-system SVG Gantt chart.
12904
13322
  *
@@ -12970,22 +13388,16 @@ class FlyGanttComponent {
12970
13388
  HEADER_UPPER_H = HEADER_UPPER_H;
12971
13389
  HEADER_LOWER_H = HEADER_LOWER_H;
12972
13390
  HANDLE_W = HANDLE_W;
12973
- MIN_LABEL_W = MIN_LABEL_W;
12974
- MAX_LABEL_W = MAX_LABEL_W;
13391
+ MIN_LABEL_W = GANTT_LABEL_W_MIN;
13392
+ MAX_LABEL_W = GANTT_LABEL_W_MAX;
12975
13393
  // ── Interaction state ─────────────────────────────────────────────────────────
12976
13394
  selectedId = signal(null, ...(ngDevMode ? [{ debugName: "selectedId" }] : /* istanbul ignore next */ []));
12977
- /** Key (`from~to~type`) of the dependency arrow the user has selected, if any. */
12978
- selectedLinkKey = signal(null, ...(ngDevMode ? [{ debugName: "selectedLinkKey" }] : /* istanbul ignore next */ []));
12979
13395
  _collapsed = signal(new Set(), ...(ngDevMode ? [{ debugName: "_collapsed" }] : /* istanbul ignore next */ []));
12980
13396
  /** Live drag preview `{id,start,end}` folded into geometry while a gesture runs. */
12981
13397
  _dragPreview = signal(null, ...(ngDevMode ? [{ debugName: "_dragPreview" }] : /* istanbul ignore next */ []));
12982
- /** Rubber-band link gesture render-space anchor + cursor, and its origin row + edge. */
12983
- linkGesture = signal(null, ...(ngDevMode ? [{ debugName: "linkGesture" }] : /* istanbul ignore next */ []));
12984
13398
  dragTooltip = signal(null, ...(ngDevMode ? [{ debugName: "dragTooltip" }] : /* istanbul ignore next */ []));
12985
13399
  /** True while a divider drag is in flight (suppresses text selection host-wide). */
12986
- resizingLabels = signal(false, ...(ngDevMode ? [{ debugName: "resizingLabels" }] : /* istanbul ignore next */ []));
12987
13400
  /** User-chosen pane width; `null` = still following the {@link labelWidth} input. */
12988
- _labelWidthOverride = signal(null, ...(ngDevMode ? [{ debugName: "_labelWidthOverride" }] : /* istanbul ignore next */ []));
12989
13401
  bodyRef = viewChild('bodySvg', ...(ngDevMode ? [{ debugName: "bodyRef" }] : /* istanbul ignore next */ []));
12990
13402
  // ── Derived model ──────────────────────────────────────────────────────────────
12991
13403
  rtl = computed(() => this.i18n.isRtl(), ...(ngDevMode ? [{ debugName: "rtl" }] : /* istanbul ignore next */ []));
@@ -12993,7 +13405,6 @@ class FlyGanttComponent {
12993
13405
  domain = computed(() => computeDomain(this.rows(), this.zoom()), ...(ngDevMode ? [{ debugName: "domain" }] : /* istanbul ignore next */ []));
12994
13406
  innerWidth = computed(() => Math.max(1, dateToX(this.domain().end, this.domain().start, this.pxPerDay())), ...(ngDevMode ? [{ debugName: "innerWidth" }] : /* istanbul ignore next */ []));
12995
13407
  /** Label-pane width actually rendered — the user's dragged width, else the input. */
12996
- effectiveLabelWidth = computed(() => clampLabelWidth(this._labelWidthOverride() ?? this.labelWidth()), ...(ngDevMode ? [{ debugName: "effectiveLabelWidth" }] : /* istanbul ignore next */ []));
12997
13408
  /** Full visible list after tree flatten/collapse. */
12998
13409
  _flat = computed(() => flattenRows(this.rows(), this._collapsed()), ...(ngDevMode ? [{ debugName: "_flat" }] : /* istanbul ignore next */ []));
12999
13410
  /** Capped list actually rendered. */
@@ -13095,7 +13506,7 @@ class FlyGanttComponent {
13095
13506
  const to = vmById.get(link.toId);
13096
13507
  if (!from || !to)
13097
13508
  continue;
13098
- const routed = this._route(from, to, link.type, rtl);
13509
+ const routed = routeLink(from, to, link.type, rtl, this.rowHeight());
13099
13510
  out.push({
13100
13511
  key: `${link.fromId}~${link.toId}~${link.type}`,
13101
13512
  fromId: link.fromId,
@@ -13151,85 +13562,31 @@ class FlyGanttComponent {
13151
13562
  }
13152
13563
  // ── Label-pane divider ─────────────────────────────────────────────────────────
13153
13564
  /**
13154
- * Pointer-drag the divider. Forward-in-time is `+x` LTR and `-x` RTL, and the label pane sits
13155
- * on the inline-start side in both, so the same sign flip that mirrors the timeline also
13156
- * mirrors "drag outward = wider".
13565
+ * The divider's own state machine, in {@link GanttLabelPane}. It is composed rather than
13566
+ * inlined because it is the one region of this component with no coupling to the chart: give it
13567
+ * a direction, a fallback width and somewhere to report a committed value and it is complete.
13568
+ * The subtle part — that "wider" flips physical sign under RTL for BOTH the drag and the arrow
13569
+ * keys — is stated once there instead of twice here.
13157
13570
  */
13571
+ _labelPane = new GanttLabelPane({
13572
+ resizable: this.resizableLabels,
13573
+ rtl: this.rtl,
13574
+ inputWidth: this.labelWidth,
13575
+ commit: (width) => this.labelWidthChange.emit(width),
13576
+ destroyRef: this.destroyRef,
13577
+ });
13578
+ /** Painted width of the label pane — the user's dragged value, else the clamped input. */
13579
+ effectiveLabelWidth = this._labelPane.width;
13580
+ /** True mid-drag; drives the host's resizing cursor/affordance. */
13581
+ resizingLabels = this._labelPane.resizing;
13158
13582
  onLabelResizePointerDown(ev) {
13159
- if (!this.resizableLabels() || ev.button !== 0)
13160
- return;
13161
- ev.preventDefault();
13162
- ev.stopPropagation();
13163
- const startClientX = ev.clientX;
13164
- const startWidth = this.effectiveLabelWidth();
13165
- const dirSign = this.rtl() ? -1 : 1;
13166
- this.resizingLabels.set(true);
13167
- const onMove = (e) => {
13168
- this._labelWidthOverride.set(clampLabelWidth(startWidth + (e.clientX - startClientX) * dirSign));
13169
- };
13170
- const onUp = () => {
13171
- document.removeEventListener('pointermove', onMove);
13172
- document.removeEventListener('pointerup', onUp);
13173
- this.resizingLabels.set(false);
13174
- const final = this.effectiveLabelWidth();
13175
- if (final !== startWidth)
13176
- this.labelWidthChange.emit(final);
13177
- };
13178
- document.addEventListener('pointermove', onMove);
13179
- document.addEventListener('pointerup', onUp);
13180
- this.destroyRef.onDestroy(() => {
13181
- document.removeEventListener('pointermove', onMove);
13182
- document.removeEventListener('pointerup', onUp);
13183
- });
13583
+ this._labelPane.onPointerDown(ev);
13184
13584
  }
13185
- /**
13186
- * Keyboard resize on the focused divider. ArrowRight/Left grow/shrink by the *inline* meaning
13187
- * of the key, so under RTL the arrow that points away from the pane is still the one that
13188
- * widens it. `Home`/`End` jump to the bounds, Enter/Escape restore the input width.
13189
- *
13190
- * Typed `Event` for the same reason as {@link onLabelRowKeydown}: Angular's strict template
13191
- * checker types `(keydown)` `$event` as `Event`, so the cast is done here once.
13192
- */
13193
13585
  onLabelResizeKeydown(ev) {
13194
- if (!this.resizableLabels())
13195
- return;
13196
- const key = ev.key;
13197
- const coarse = ev.shiftKey;
13198
- const step = coarse ? LABEL_STEP_COARSE : LABEL_STEP;
13199
- const grow = this.rtl() ? 'ArrowLeft' : 'ArrowRight';
13200
- const shrink = this.rtl() ? 'ArrowRight' : 'ArrowLeft';
13201
- let next;
13202
- if (key === grow)
13203
- next = this.effectiveLabelWidth() + step;
13204
- else if (key === shrink)
13205
- next = this.effectiveLabelWidth() - step;
13206
- else if (key === 'Home')
13207
- next = MIN_LABEL_W;
13208
- else if (key === 'End')
13209
- next = MAX_LABEL_W;
13210
- else if (key === 'Enter' || key === 'Escape') {
13211
- // Stop the grid-level Enter handler from also activating the selected row.
13212
- ev.preventDefault();
13213
- ev.stopPropagation();
13214
- this.resetLabelWidth();
13215
- return;
13216
- }
13217
- else
13218
- return;
13219
- ev.preventDefault();
13220
- ev.stopPropagation();
13221
- const clamped = clampLabelWidth(next);
13222
- if (clamped === this.effectiveLabelWidth())
13223
- return;
13224
- this._labelWidthOverride.set(clamped);
13225
- this.labelWidthChange.emit(clamped);
13586
+ this._labelPane.onKeydown(ev);
13226
13587
  }
13227
- /** Drop the user's dragged width and fall back to the {@link labelWidth} input. */
13228
13588
  resetLabelWidth() {
13229
- if (this._labelWidthOverride() === null)
13230
- return;
13231
- this._labelWidthOverride.set(null);
13232
- this.labelWidthChange.emit(this.effectiveLabelWidth());
13589
+ this._labelPane.reset();
13233
13590
  }
13234
13591
  // ── Group collapse ─────────────────────────────────────────────────────────────
13235
13592
  isCollapsed(id) {
@@ -13260,7 +13617,7 @@ class FlyGanttComponent {
13260
13617
  this.selectedId.set(id);
13261
13618
  // A row and a link are alternative selections — picking one drops the other, so Delete is
13262
13619
  // never ambiguous about which thing it would remove.
13263
- this.selectedLinkKey.set(null);
13620
+ this._links.clearSelection();
13264
13621
  this.rowClick.emit(id);
13265
13622
  }
13266
13623
  onRowDblClick(id) {
@@ -13269,14 +13626,14 @@ class FlyGanttComponent {
13269
13626
  // ── Dependency arrow selection + removal ───────────────────────────────────────
13270
13627
  onLinkClick(ev, vm) {
13271
13628
  ev.stopPropagation();
13272
- this.selectedLinkKey.set(this.selectedLinkKey() === vm.key ? null : vm.key);
13629
+ this._links.toggleSelection(vm.key);
13273
13630
  }
13274
13631
  deleteLink(ev, vm) {
13275
13632
  ev.stopPropagation();
13276
13633
  ev.preventDefault();
13277
13634
  if (this.readonly())
13278
13635
  return;
13279
- this.selectedLinkKey.set(null);
13636
+ this._links.clearSelection();
13280
13637
  this.dependencyDelete.emit({ fromId: vm.fromId, toId: vm.toId, type: vm.type });
13281
13638
  }
13282
13639
  // ── Keyboard ───────────────────────────────────────────────────────────────────
@@ -13413,54 +13770,27 @@ class FlyGanttComponent {
13413
13770
  }
13414
13771
  // ── Pointer drag: dependency create ──────────────────────────────────────────────
13415
13772
  /**
13416
- * Start a link gesture from one end of a bar. Which end it started on, plus which end of the
13417
- * target row it is dropped nearest to, is what selects the {@link GanttDependencyType}so all
13418
- * four MS-Project relationships are reachable with the same single drag.
13773
+ * The dependency-drawing gesture, in {@link GanttLinkGestures}. Composed rather than inlined
13774
+ * because it is a self-contained second gesture whose interesting part is a RULE the end you
13775
+ * start on plus the end you drop nearest, selects the relationship type — which reads far better beside
13776
+ * its own hit-testing than in the middle of this component.
13419
13777
  */
13778
+ _links = new GanttLinkGestures({
13779
+ readonly: this.readonly,
13780
+ rows: this.rowVms,
13781
+ bodySvg: () => this.bodyRef()?.nativeElement ?? null,
13782
+ createLink: (dependency) => this.dependencyCreate.emit(dependency),
13783
+ destroyRef: this.destroyRef,
13784
+ });
13785
+ /** Key (`from~to~type`) of the dependency arrow the user has selected, if any. */
13786
+ selectedLinkKey = this._links.selectedKey;
13787
+ /** The in-flight rubber-band gesture, or `null` — the template draws it when present. */
13788
+ linkGesture = this._links.gesture;
13789
+ /** Rubber-band path for an in-flight link gesture (render space). */
13790
+ linkGesturePath = this._links.path;
13420
13791
  onLinkPointerDown(ev, vm, anchor) {
13421
- if (this.readonly() || vm.flat.row.readonly || ev.button !== 0)
13422
- return;
13423
- ev.preventDefault();
13424
- ev.stopPropagation();
13425
- const local = this._toBodyPoint(ev);
13426
- const x0 = vm.shape === 'milestone' ? vm.pointX : anchor === 'finish' ? vm.endX : vm.startX;
13427
- this.linkGesture.set({ fromId: vm.flat.row.id, anchor, x0, y0: vm.midY, x1: local.x, y1: local.y });
13428
- const onMove = (e) => {
13429
- const p = this._toBodyPoint(e);
13430
- const g = this.linkGesture();
13431
- if (g)
13432
- this.linkGesture.set({ ...g, x1: p.x, y1: p.y });
13433
- };
13434
- const onUp = (e) => {
13435
- document.removeEventListener('pointermove', onMove);
13436
- document.removeEventListener('pointerup', onUp);
13437
- const g = this.linkGesture();
13438
- this.linkGesture.set(null);
13439
- if (!g)
13440
- return;
13441
- const target = this._dropTargetAt(e.clientX, e.clientY);
13442
- if (target && target.id !== g.fromId) {
13443
- this.dependencyCreate.emit({
13444
- fromId: g.fromId,
13445
- toId: target.id,
13446
- type: linkTypeFor(g.anchor, target.anchor),
13447
- });
13448
- }
13449
- };
13450
- document.addEventListener('pointermove', onMove);
13451
- document.addEventListener('pointerup', onUp);
13452
- this.destroyRef.onDestroy(() => {
13453
- document.removeEventListener('pointermove', onMove);
13454
- document.removeEventListener('pointerup', onUp);
13455
- });
13792
+ this._links.start(ev, vm, anchor);
13456
13793
  }
13457
- /** Rubber-band path for an in-flight link gesture (render space). */
13458
- linkGesturePath = computed(() => {
13459
- const g = this.linkGesture();
13460
- if (!g)
13461
- return null;
13462
- return `M ${g.x0} ${g.y0} L ${g.x1} ${g.y1}`;
13463
- }, ...(ngDevMode ? [{ debugName: "linkGesturePath" }] : /* istanbul ignore next */ []));
13464
13794
  // ── Internals ────────────────────────────────────────────────────────────────────
13465
13795
  /**
13466
13796
  * Resolve a row's `backgroundColor` into the low-alpha band colour actually painted.
@@ -13481,70 +13811,6 @@ class FlyGanttComponent {
13481
13811
  // Reflect each cell: its inline-start edge becomes innerWidth - (x + width).
13482
13812
  return ticks.map((t) => ({ ...t, x: w - t.x - t.width }));
13483
13813
  }
13484
- /**
13485
- * Route a dependency elbow for any of the four relationship kinds (render space).
13486
- *
13487
- * The kind names the two endpoints, so it decides both the anchor x's and the direction the
13488
- * arrow travels: leaving a *finish* edge moves forward in time, leaving a *start* edge moves
13489
- * backward; entering a *start* edge approaches from behind, entering a *finish* edge from
13490
- * ahead. `fwd` folds RTL in, so none of that branches on direction.
13491
- *
13492
- * One turn suffices when the entry stub lies ahead of the exit stub in the approach
13493
- * direction. When it does not — overlapping or reversed bars, which SS/FF/SF hit constantly —
13494
- * a straight drop would cut back through the bars, so the elbow detours via the gap between
13495
- * the two rows instead.
13496
- */
13497
- _route(from, to, type, rtl) {
13498
- const fwd = rtl ? -1 : 1;
13499
- const fromFinish = type === 'FS' || type === 'FF';
13500
- const toStart = type === 'FS' || type === 'SS';
13501
- const x1 = from.shape === 'milestone' ? from.pointX : fromFinish ? from.endX : from.startX;
13502
- const x2 = to.shape === 'milestone' ? to.pointX : toStart ? to.startX : to.endX;
13503
- const y1 = from.midY;
13504
- const y2 = to.midY;
13505
- const d1 = (fromFinish ? 1 : -1) * fwd; // exit direction
13506
- const d2 = (toStart ? 1 : -1) * fwd; // approach direction
13507
- const exit = x1 + LINK_STUB * d1;
13508
- const entry = x2 - LINK_STUB * d2;
13509
- if ((entry - exit) * d2 >= 0) {
13510
- return { path: `M ${x1} ${y1} H ${exit} V ${y2} H ${x2}`, badgeX: exit, badgeY: (y1 + y2) / 2 };
13511
- }
13512
- // Distinct rows always differ in y; the guard only keeps a degenerate input off the bar.
13513
- const yMid = y1 === y2 ? y1 - this.rowHeight() / 2 : (y1 + y2) / 2;
13514
- return {
13515
- path: `M ${x1} ${y1} H ${exit} V ${yMid} H ${entry} V ${y2} H ${x2}`,
13516
- badgeX: entry,
13517
- badgeY: yMid,
13518
- };
13519
- }
13520
- /** Convert a pointer event to a coordinate inside the body SVG (render space). */
13521
- _toBodyPoint(ev) {
13522
- const svg = this.bodyRef()?.nativeElement;
13523
- if (!svg)
13524
- return { x: 0, y: 0 };
13525
- const rect = svg.getBoundingClientRect();
13526
- return { x: ev.clientX - rect.left, y: ev.clientY - rect.top };
13527
- }
13528
- /**
13529
- * The row a link gesture was dropped on **and which of its ends** the drop landed nearest —
13530
- * the second half of the type derivation. A milestone has no width, so both of its ends are
13531
- * the same point and it is reported as `start`, giving the FS/SS pair anyone actually wants.
13532
- */
13533
- _dropTargetAt(clientX, clientY) {
13534
- const el = document.elementFromPoint(clientX, clientY);
13535
- const holder = el?.closest('[data-row-id]');
13536
- const id = holder?.getAttribute('data-row-id');
13537
- if (!id)
13538
- return null;
13539
- const vm = this.rowVms().find((v) => v.flat.row.id === id);
13540
- if (!vm || vm.shape === 'milestone' || vm.shape === 'empty')
13541
- return { id, anchor: 'start' };
13542
- const x = this._toBodyPoint({ clientX, clientY }).x;
13543
- return {
13544
- id,
13545
- anchor: Math.abs(x - vm.endX) < Math.abs(x - vm.startX) ? 'finish' : 'start',
13546
- };
13547
- }
13548
13814
  _ariaFor(row, start, end, shape) {
13549
13815
  const t = (k, p) => this.i18n.t(k, p);
13550
13816
  if (shape === 'milestone') {
@@ -19370,6 +19636,29 @@ function flyUnwrapCurrencies(res) {
19370
19636
  const data = res?.data;
19371
19637
  return Array.isArray(data) ? data : [];
19372
19638
  }
19639
+ /** The platform endpoint serving the CALLING TENANT's default currency. Relative, like its
19640
+ * catalogue sibling — the host's gateway + auth interceptors apply, and the answer is scoped to
19641
+ * whatever tenant that auth resolves to. */
19642
+ const FLY_CURRENCY_DEFAULT_ENDPOINT = '/api/currencies/default';
19643
+ /**
19644
+ * Unwraps the single-row shape of `GET /api/currencies/default`.
19645
+ *
19646
+ * `null` is a SUCCESS answer, not a failure: a tenant with no country has no default currency,
19647
+ * and a platform-scoped caller has no tenant whose currency could be meant. Anything that is not
19648
+ * recognisably a currency row — an envelope with `data: null`, an error body, an HTML login page
19649
+ * from a misrouted request — collapses to `null` for the same reason
19650
+ * {@link flyUnwrapCurrencies} returns `[]`: a picker must degrade to "no preselection", never
19651
+ * preselect something the platform did not vouch for.
19652
+ */
19653
+ function flyUnwrapCurrency(res) {
19654
+ const candidate = res !== null && typeof res === 'object' && 'data' in res
19655
+ ? res.data
19656
+ : res;
19657
+ if (candidate === null || typeof candidate !== 'object')
19658
+ return null;
19659
+ const row = candidate;
19660
+ return typeof row.code === 'string' && row.code.length > 0 ? candidate : null;
19661
+ }
19373
19662
  /**
19374
19663
  * **`FlyCurrencyCatalogService`** — the one ISO-code → {@link FlyCurrency} adapter for the
19375
19664
  * platform currency catalogue (`GET /api/currencies/brief`).
@@ -19490,6 +19779,8 @@ let _flyCurrencySelectorUid = 0;
19490
19779
  * Re-exported from {@link FlyCurrencyCatalogService}'s module so the picker and the display-only
19491
19780
  * catalogue cannot drift on which route serves the rows. */
19492
19781
  const CURRENCIES_BRIEF_ENDPOINT = FLY_CURRENCIES_BRIEF_ENDPOINT;
19782
+ /** The platform endpoint behind {@link FlyCurrencySelectorComponent.applyTenantDefault}. */
19783
+ const CURRENCY_DEFAULT_ENDPOINT = FLY_CURRENCY_DEFAULT_ENDPOINT;
19493
19784
  /** Cached answer of {@link probeFlagEmojiSupport} — the measurement is stable for a document's lifetime. */
19494
19785
  let _flagEmojiSupport = null;
19495
19786
  /**
@@ -19600,6 +19891,30 @@ class FlyCurrencySelectorComponent {
19600
19891
  currencies = input(null, ...(ngDevMode ? [{ debugName: "currencies" }] : /* istanbul ignore next */ []));
19601
19892
  /** Host-supplied loader, used when `[currencies]` is absent. Falls back to `GET /api/currencies/brief`. */
19602
19893
  fetchFn = input(null, ...(ngDevMode ? [{ debugName: "fetchFn" }] : /* istanbul ignore next */ []));
19894
+ /**
19895
+ * Pre-fill an EMPTY single-select with the calling tenant's default currency
19896
+ * (`GET /api/currencies/default`, or `[tenantDefaultFetchFn]`). On by default.
19897
+ *
19898
+ * An amount is nearly always denominated in the tenant's reporting currency, and a form that
19899
+ * opens empty makes every user restate a fact the platform already holds — while an untouched
19900
+ * field silently saves null, which downstream money formatting cannot render at all.
19901
+ *
19902
+ * Deliberately narrow. It applies only when ALL of these hold, because outside them a
19903
+ * preselection would be a guess rather than a default:
19904
+ * - `mode="single"` — a multi-select accumulates a SET, and seeding one member of a set
19905
+ * states something the tenant currency does not say.
19906
+ * - not `locked()` — a frozen value is authoritative; proposing one contradicts the lock.
19907
+ * - the control is still empty — a written value always wins.
19908
+ *
19909
+ * It fires at most once per instance: clearing the field by hand latches it off, so the value
19910
+ * cannot reappear and read as the clear having not registered.
19911
+ *
19912
+ * A host that supplied `[currencies]` or `[fetchFn]` is never taken to the platform endpoint —
19913
+ * see {@link tenantDefaultFetchFn}, which is how such a host opts back in.
19914
+ */
19915
+ applyTenantDefault = input(true, ...(ngDevMode ? [{ debugName: "applyTenantDefault" }] : /* istanbul ignore next */ []));
19916
+ /** Host-supplied loader for {@link applyTenantDefault}. Falls back to `GET /api/currencies/default`. */
19917
+ tenantDefaultFetchFn = input(null, ...(ngDevMode ? [{ debugName: "tenantDefaultFetchFn" }] : /* istanbul ignore next */ []));
19603
19918
  /** Restrict the offered set to these ISO 4217 codes (case-insensitive). Empty = offer everything. */
19604
19919
  allowedCodes = input([], ...(ngDevMode ? [{ debugName: "allowedCodes" }] : /* istanbul ignore next */ []));
19605
19920
  /**
@@ -19660,6 +19975,12 @@ class FlyCurrencySelectorComponent {
19660
19975
  _selectedCodes = signal([], ...(ngDevMode ? [{ debugName: "_selectedCodes" }] : /* istanbul ignore next */ []));
19661
19976
  _cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "_cvaDisabled" }] : /* istanbul ignore next */ []));
19662
19977
  _fetchStarted = false;
19978
+ /** Guards the once-per-instance tenant-default resolve. */
19979
+ _tenantDefaultStarted = false;
19980
+ /** Latches once the default has been applied, or ruled out by any commit (pick, clear, write). */
19981
+ _tenantDefaultSettled = false;
19982
+ /** The resolved row, kept so a `writeValue(null)` arriving AFTER the fetch can still use it. */
19983
+ _tenantDefaultRow = null;
19663
19984
  searchEl;
19664
19985
  triggerEl;
19665
19986
  _onChange = () => undefined;
@@ -19675,6 +19996,34 @@ class FlyCurrencySelectorComponent {
19675
19996
  this._fetchStarted = true;
19676
19997
  this._load();
19677
19998
  });
19999
+ // A control that HOLDS a value must resolve it, whether or not the user ever opens the
20000
+ // panel: `selected()` maps codes through the loaded rows, so a saved code with no rows
20001
+ // renders as the placeholder — an edit form reading "Select a currency" over a project that
20002
+ // is denominated in AED. The lazy rule is still intact for the case it was written for: an
20003
+ // EMPTY selector on a tab nobody opens fetches nothing.
20004
+ effect(() => {
20005
+ if (this._fetchStarted)
20006
+ return;
20007
+ if (this.currencies() !== null)
20008
+ return;
20009
+ if (this._selectedCodes().length === 0)
20010
+ return;
20011
+ this._fetchStarted = true;
20012
+ this._load();
20013
+ });
20014
+ // The tenant default is resolved EAGERLY, unlike the catalogue: its whole job is to be in
20015
+ // place before the user looks at the field, so deferring it to first open would defeat it.
20016
+ // The cost is bounded - one small request per defaulting selector, against an endpoint that
20017
+ // returns a single row. An effect rather than the constructor body so `applyTenantDefault`
20018
+ // and `locked` are read AFTER the host's bindings are applied, not at their declared values.
20019
+ effect(() => {
20020
+ if (this._tenantDefaultStarted)
20021
+ return;
20022
+ if (!this.applyTenantDefault() || this.isMulti() || this.locked())
20023
+ return;
20024
+ this._tenantDefaultStarted = true;
20025
+ this._loadTenantDefault();
20026
+ });
19678
20027
  }
19679
20028
  // ── Derived view state ─────────────────────────────────────────────────────
19680
20029
  effectiveDisabled = computed(() => this.disabled() || this._cvaDisabled(), ...(ngDevMode ? [{ debugName: "effectiveDisabled" }] : /* istanbul ignore next */ []));
@@ -19899,13 +20248,69 @@ class FlyCurrencySelectorComponent {
19899
20248
  this._loaded.set(rows);
19900
20249
  });
19901
20250
  }
20251
+ _loadTenantDefault() {
20252
+ const fetch = this.tenantDefaultFetchFn();
20253
+ // A host that took over the CATALOGUE owns the default too: `[currencies]`/`[fetchFn]` are
20254
+ // the "do not reach the platform routes" declaration (offline builds, fixtures, apps that
20255
+ // proxy reference data), and honouring it for the rows while quietly firing a platform
20256
+ // request for the default would break exactly the consumers those inputs exist for. Such a
20257
+ // host opts back in by supplying `[tenantDefaultFetchFn]`.
20258
+ const hostOwnsData = this.currencies() !== null || this.fetchFn() !== null;
20259
+ const source = fetch
20260
+ ? fetch()
20261
+ : this.http && !hostOwnsData
20262
+ ? this.http.get(CURRENCY_DEFAULT_ENDPOINT).pipe(map(flyUnwrapCurrency))
20263
+ : null;
20264
+ // No transport and no loader is not an error state here: an offline surface simply has no
20265
+ // default. Nothing is surfaced to the user - unlike a failed CATALOGUE load, which must be
20266
+ // retryable because it leaves the control unusable.
20267
+ if (!source)
20268
+ return;
20269
+ source.pipe(catchError$1(() => of(null))).subscribe((row) => {
20270
+ this._tenantDefaultRow = row;
20271
+ this._applyTenantDefaultIfEmpty();
20272
+ });
20273
+ }
20274
+ /**
20275
+ * Applies the resolved default to a still-empty control.
20276
+ *
20277
+ * Called from both ends of a race with no fixed winner: the fetch may land before the forms
20278
+ * layer writes, or after it writes the empty value a form is built with.
20279
+ */
20280
+ _applyTenantDefaultIfEmpty() {
20281
+ if (this._tenantDefaultSettled)
20282
+ return;
20283
+ if (!this.applyTenantDefault() || this.isMulti() || this.locked())
20284
+ return;
20285
+ const row = this._tenantDefaultRow;
20286
+ if (!row)
20287
+ return;
20288
+ if (this._selectedCodes().length > 0)
20289
+ return;
20290
+ // Seed the row so the trigger can RENDER the pick straight away. Without this the selection
20291
+ // would be invisible until the catalogue loads - which is lazy, on first open - so the field
20292
+ // would look untouched while its form value said otherwise. `_load` replaces the whole set
20293
+ // on success, and leaves this seed alone on failure.
20294
+ if (this.currencies() === null && this._loaded().length === 0)
20295
+ this._loaded.set([row]);
20296
+ this._commit([row.code]);
20297
+ }
19902
20298
  // ── ControlValueAccessor ───────────────────────────────────────────────────
19903
20299
  writeValue(value) {
19904
20300
  if (value === null || value === undefined) {
19905
20301
  this._selectedCodes.set([]);
20302
+ // An empty write re-tries the default - this is the common `patchValue`-after-load path,
20303
+ // and on a fresh form it is the write that arrives BEFORE the fetch has landed.
20304
+ this._applyTenantDefaultIfEmpty();
19906
20305
  return;
19907
20306
  }
19908
- this._selectedCodes.set(typeof value === 'string' ? [value] : [...value]);
20307
+ const codes = typeof value === 'string' ? [value] : [...value];
20308
+ this._selectedCodes.set(codes);
20309
+ // A written value is the host's own answer; there is no hole left for a default to fill.
20310
+ if (codes.length > 0)
20311
+ this._tenantDefaultSettled = true;
20312
+ else
20313
+ this._applyTenantDefaultIfEmpty();
19909
20314
  }
19910
20315
  registerOnChange(fn) {
19911
20316
  this._onChange = fn;
@@ -19922,6 +20327,10 @@ class FlyCurrencySelectorComponent {
19922
20327
  return this.isMulti() ? [...codes] : (codes[0] ?? null);
19923
20328
  }
19924
20329
  _commit(codes) {
20330
+ // Every user-driven change routes through here - pick, toggle, remove, clear - so this is the
20331
+ // one place that has to latch the default off. A cleared field that refilled itself would be
20332
+ // indistinguishable from the clear never having registered.
20333
+ this._tenantDefaultSettled = true;
19925
20334
  this._selectedCodes.set([...codes]);
19926
20335
  this._onChange(this._controlValue());
19927
20336
  this.selectionChange.emit([...codes]);
@@ -19931,7 +20340,7 @@ class FlyCurrencySelectorComponent {
19931
20340
  this._onTouched();
19932
20341
  }
19933
20342
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyCurrencySelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
19934
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyCurrencySelectorComponent, isStandalone: true, selector: "fly-currency-selector", inputs: { mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, currencies: { classPropertyName: "currencies", publicName: "currencies", isSignal: true, isRequired: false, transformFunction: null }, fetchFn: { classPropertyName: "fetchFn", publicName: "fetchFn", isSignal: true, isRequired: false, transformFunction: null }, allowedCodes: { classPropertyName: "allowedCodes", publicName: "allowedCodes", isSignal: true, isRequired: false, transformFunction: null }, pinnedCodes: { classPropertyName: "pinnedCodes", publicName: "pinnedCodes", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, locked: { classPropertyName: "locked", publicName: "locked", isSignal: true, isRequired: false, transformFunction: null }, lockedReasonKey: { classPropertyName: "lockedReasonKey", publicName: "lockedReasonKey", isSignal: true, isRequired: false, transformFunction: null }, lockedReasonParams: { classPropertyName: "lockedReasonParams", publicName: "lockedReasonParams", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange", selectionDetailChange: "selectionDetailChange", openedChange: "openedChange" }, host: { properties: { "class.fly-currency-selector--open": "isOpen()", "class.fly-currency-selector--disabled": "effectiveDisabled()", "class.fly-currency-selector--locked": "locked()" }, classAttribute: "fly-currency-selector" }, providers: [
20343
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyCurrencySelectorComponent, isStandalone: true, selector: "fly-currency-selector", inputs: { mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, currencies: { classPropertyName: "currencies", publicName: "currencies", isSignal: true, isRequired: false, transformFunction: null }, fetchFn: { classPropertyName: "fetchFn", publicName: "fetchFn", isSignal: true, isRequired: false, transformFunction: null }, applyTenantDefault: { classPropertyName: "applyTenantDefault", publicName: "applyTenantDefault", isSignal: true, isRequired: false, transformFunction: null }, tenantDefaultFetchFn: { classPropertyName: "tenantDefaultFetchFn", publicName: "tenantDefaultFetchFn", isSignal: true, isRequired: false, transformFunction: null }, allowedCodes: { classPropertyName: "allowedCodes", publicName: "allowedCodes", isSignal: true, isRequired: false, transformFunction: null }, pinnedCodes: { classPropertyName: "pinnedCodes", publicName: "pinnedCodes", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, locked: { classPropertyName: "locked", publicName: "locked", isSignal: true, isRequired: false, transformFunction: null }, lockedReasonKey: { classPropertyName: "lockedReasonKey", publicName: "lockedReasonKey", isSignal: true, isRequired: false, transformFunction: null }, lockedReasonParams: { classPropertyName: "lockedReasonParams", publicName: "lockedReasonParams", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange", selectionDetailChange: "selectionDetailChange", openedChange: "openedChange" }, host: { properties: { "class.fly-currency-selector--open": "isOpen()", "class.fly-currency-selector--disabled": "effectiveDisabled()", "class.fly-currency-selector--locked": "locked()" }, classAttribute: "fly-currency-selector" }, providers: [
19935
20344
  {
19936
20345
  provide: NG_VALUE_ACCESSOR,
19937
20346
  useExisting: forwardRef(() => FlyCurrencySelectorComponent),
@@ -19953,7 +20362,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
19953
20362
  '[class.fly-currency-selector--disabled]': 'effectiveDisabled()',
19954
20363
  '[class.fly-currency-selector--locked]': 'locked()',
19955
20364
  }, template: "<div\r\n class=\"fly-currency-selector__wrap\"\r\n [flyClickOutsideEnabled]=\"isOpen()\"\r\n (flyClickOutside)=\"close()\"\r\n>\r\n @if (locked()) {\r\n <!-- \u2500\u2500 Locked readout \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n NOT a dimmed `disabled` trigger: there is no dropdown affordance here at all \u2014 the\r\n value is frozen on purpose, not merely unavailable right now. `role=\"group\"` +\r\n `aria-label` name the field the way the combobox trigger's `aria-label` does; the\r\n reason is REAL, always-rendered text (not a `title` attribute, which many screen\r\n readers never announce and which no sighted user sees without hovering) and is\r\n additionally wired via `aria-describedby` for AT that reads by relationship rather\r\n than document order. -->\r\n <div\r\n class=\"fly-currency-selector__locked\"\r\n role=\"group\"\r\n [attr.aria-label]=\"ariaLabel() || placeholderText()\"\r\n [attr.aria-describedby]=\"lockedReasonId\"\r\n >\r\n @if (selected().length === 0) {\r\n <span class=\"fly-currency-selector__placeholder\">{{ placeholderText() }}</span>\r\n } @else {\r\n <span class=\"fly-currency-selector__chips\">\r\n @for (c of selected(); track c.code) {\r\n <span class=\"fly-currency-selector__chip\">\r\n <span class=\"fly-currency-selector__tile\" aria-hidden=\"true\">\r\n @if (flagsRenderable() && c.flag) {\r\n <span class=\"fly-currency-selector__tile-flag\">{{ c.flag }}</span>\r\n } @else {\r\n <span class=\"fly-currency-selector__tile-code\">{{ tileCode(c) }}</span>\r\n }\r\n </span>\r\n <span class=\"fly-currency-selector__chip-code\">{{ c.code }}</span>\r\n <span class=\"fly-currency-selector__chip-symbol\" aria-hidden=\"true\">{{ c.symbol }}</span>\r\n </span>\r\n }\r\n </span>\r\n }\r\n <span class=\"fly-currency-selector__lock-icon\" aria-hidden=\"true\">&#128274;</span>\r\n </div>\r\n <p class=\"fly-currency-selector__locked-reason\" [id]=\"lockedReasonId\">{{ lockedReasonText() }}</p>\r\n } @else {\r\n\r\n <!-- \u2500\u2500 Trigger \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n `aria-label` is never null: a `combobox` does NOT take its accessible name from its\r\n contents the way a plain button does, so the chips/placeholder painted inside it name\r\n nothing. Absent a consumer label the localized placeholder supplies the control's\r\n purpose; its VALUE is still announced from the contents. -->\r\n <button\r\n #triggerRef\r\n type=\"button\"\r\n [id]=\"triggerId\"\r\n class=\"fly-currency-selector__trigger\"\r\n role=\"combobox\"\r\n aria-haspopup=\"listbox\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n [attr.aria-controls]=\"listboxId\"\r\n [attr.aria-label]=\"ariaLabel() || placeholderText()\"\r\n [class.fly-currency-selector__trigger--empty]=\"!hasSelection()\"\r\n [disabled]=\"effectiveDisabled()\"\r\n (click)=\"toggleOpen()\"\r\n (keydown)=\"onTriggerKeydown($event)\"\r\n >\r\n @if (selected().length === 0) {\r\n <span class=\"fly-currency-selector__placeholder\">{{ placeholderText() }}</span>\r\n } @else {\r\n <span class=\"fly-currency-selector__chips\">\r\n @for (c of selected(); track c.code) {\r\n <span class=\"fly-currency-selector__chip\">\r\n <span class=\"fly-currency-selector__tile\" aria-hidden=\"true\">\r\n @if (flagsRenderable() && c.flag) {\r\n <span class=\"fly-currency-selector__tile-flag\">{{ c.flag }}</span>\r\n } @else {\r\n <span class=\"fly-currency-selector__tile-code\">{{ tileCode(c) }}</span>\r\n }\r\n </span>\r\n <span class=\"fly-currency-selector__chip-code\">{{ c.code }}</span>\r\n <span class=\"fly-currency-selector__chip-symbol\" aria-hidden=\"true\">{{ c.symbol }}</span>\r\n @if (isMulti() && !effectiveDisabled()) {\r\n <span\r\n class=\"fly-currency-selector__chip-remove\"\r\n role=\"button\"\r\n tabindex=\"-1\"\r\n [attr.aria-label]=\"'\u2715 ' + c.code\"\r\n (click)=\"remove(c.code, $event)\"\r\n (mousedown)=\"$event.preventDefault()\"\r\n >\u2715</span>\r\n }\r\n </span>\r\n }\r\n </span>\r\n }\r\n\r\n @if (clearable() && hasSelection() && !effectiveDisabled()) {\r\n <span\r\n class=\"fly-currency-selector__clear\"\r\n role=\"button\"\r\n tabindex=\"-1\"\r\n [attr.aria-label]=\"clearText()\"\r\n [title]=\"clearText()\"\r\n (click)=\"clear($event)\"\r\n (mousedown)=\"$event.preventDefault()\"\r\n >\u2715</span>\r\n }\r\n\r\n <span class=\"fly-currency-selector__chevron\" aria-hidden=\"true\">\u25BE</span>\r\n </button>\r\n\r\n <!-- \u2500\u2500 Panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n @if (isOpen()) {\r\n <div class=\"fly-currency-selector__panel\" tabindex=\"-1\" (keydown)=\"onPanelKeydown($event)\">\r\n <div class=\"fly-currency-selector__search\">\r\n <input\r\n #searchRef\r\n type=\"text\"\r\n class=\"fly-currency-selector__search-input\"\r\n role=\"combobox\"\r\n aria-autocomplete=\"list\"\r\n [attr.aria-expanded]=\"true\"\r\n [attr.aria-controls]=\"listboxId\"\r\n [attr.aria-activedescendant]=\"activeDescendant() || null\"\r\n [attr.aria-label]=\"searchPlaceholderText()\"\r\n [placeholder]=\"searchPlaceholderText()\"\r\n [value]=\"searchTerm()\"\r\n (input)=\"onSearchInput($any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n @if (loading()) {\r\n <div class=\"fly-currency-selector__status\" role=\"status\">{{ loadingText() }}</div>\r\n } @else if (loadFailed()) {\r\n <div class=\"fly-currency-selector__status fly-currency-selector__status--error\" role=\"alert\">\r\n <span>{{ errorText() }}</span>\r\n <button type=\"button\" class=\"fly-currency-selector__retry\" (click)=\"retry()\">\r\n {{ retryText() }}\r\n </button>\r\n </div>\r\n }\r\n\r\n <div\r\n class=\"fly-currency-selector__listbox\"\r\n role=\"listbox\"\r\n [id]=\"listboxId\"\r\n [attr.aria-multiselectable]=\"isMulti() ? true : null\"\r\n [attr.aria-activedescendant]=\"activeDescendant() || null\"\r\n [attr.aria-label]=\"ariaLabel() || null\"\r\n >\r\n @if (pinned().length > 0) {\r\n <div class=\"fly-currency-selector__group\" role=\"presentation\">{{ pinnedGroupText() }}</div>\r\n @for (c of pinned(); track c.code) {\r\n <ng-container *ngTemplateOutlet=\"row; context: { $implicit: c }\" />\r\n }\r\n @if (rest().length > 0) {\r\n <div class=\"fly-currency-selector__group\" role=\"presentation\">{{ allGroupText() }}</div>\r\n }\r\n }\r\n @for (c of rest(); track c.code) {\r\n <ng-container *ngTemplateOutlet=\"row; context: { $implicit: c }\" />\r\n }\r\n\r\n @if (!loading() && !loadFailed() && filtered().length === 0) {\r\n <div class=\"fly-currency-selector__status\" role=\"presentation\">{{ noResultsText() }}</div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n }\r\n</div>\r\n\r\n<!-- \u2500\u2500 One option row, shared by the pinned and full groups \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n<ng-template #row let-c>\r\n <div\r\n class=\"fly-currency-selector__option\"\r\n role=\"option\"\r\n tabindex=\"-1\"\r\n [id]=\"optionId(navIndexOf(c.code))\"\r\n [attr.aria-selected]=\"isSelected(c.code)\"\r\n [class.fly-currency-selector__option--selected]=\"isSelected(c.code)\"\r\n [class.fly-currency-selector__option--active]=\"navIndexOf(c.code) === activeIndex()\"\r\n (click)=\"pick(c)\"\r\n (keydown.enter)=\"pick(c)\"\r\n (mouseenter)=\"onOptionHover(navIndexOf(c.code))\"\r\n >\r\n <span class=\"fly-currency-selector__tile\" aria-hidden=\"true\">\r\n @if (flagsRenderable() && c.flag) {\r\n <span class=\"fly-currency-selector__tile-flag\">{{ c.flag }}</span>\r\n } @else {\r\n <span class=\"fly-currency-selector__tile-code\">{{ tileCode(c) }}</span>\r\n }\r\n </span>\r\n\r\n <span class=\"fly-currency-selector__option-text\">\r\n <span class=\"fly-currency-selector__option-code\">{{ c.code }}</span>\r\n <span class=\"fly-currency-selector__option-name\">{{ c.name }}</span>\r\n </span>\r\n\r\n <span class=\"fly-currency-selector__option-symbol\" aria-hidden=\"true\">{{ c.symbol }}</span>\r\n\r\n <span class=\"fly-currency-selector__check\" aria-hidden=\"true\">\r\n @if (isSelected(c.code)) { \u2713 }\r\n </span>\r\n </div>\r\n</ng-template>\r\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--surface-card, #fff);--_surface-panel: var(--glass-bg-elevated, var(--surface-card, #fff));--_panel-blur: var(--glass-blur, 40px);--_surface-hover: var(--surface-hover, #f1f5f9);--_surface-active: var(--surface-active, #eef2ff);--_border: var(--surface-border, #e2e8f0);--_text: var(--text-color, #0f172a);--_text-subtle: var(--text-color-secondary, #64748b);--_fill: var(--fill-tertiary, #f3f4f6);--_primary: var(--primary-color);--_danger: var(--system-red, #ef4444);--_radius: 8px;display:inline-block;inline-size:100%;min-inline-size:11rem;font-size:13px;color:var(--_text)}.fly-currency-selector__wrap{position:relative;inline-size:100%}.fly-currency-selector__trigger{display:flex;align-items:center;gap:8px;inline-size:100%;min-block-size:2.25rem;padding-block:4px;padding-inline:8px;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);color:var(--_text);font:inherit;text-align:start;cursor:pointer}.fly-currency-selector__trigger:focus-visible{outline:2px solid var(--_primary);outline-offset:2px}.fly-currency-selector__trigger:disabled{opacity:.55;cursor:not-allowed}.fly-currency-selector__placeholder{flex:1 1 auto;padding-inline-start:4px;color:var(--_text-subtle);overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.fly-currency-selector__chips{display:flex;flex:1 1 auto;flex-wrap:wrap;gap:4px;min-inline-size:0}.fly-currency-selector__chip{display:inline-flex;align-items:center;gap:5px;padding-block:2px;padding-inline:3px 7px;border-radius:999px;background:var(--_fill);font-size:12px}.fly-currency-selector__chip-code{font-weight:700;letter-spacing:.02em}.fly-currency-selector__chip-symbol{color:var(--_text-subtle)}.fly-currency-selector__chip-remove{padding:1px 3px;border-radius:50%;color:var(--_text-subtle);font-size:10px;line-height:1;cursor:pointer}.fly-currency-selector__chip-remove:hover{background:color-mix(in srgb,var(--_danger) 18%,transparent);color:var(--_danger)}.fly-currency-selector__clear,.fly-currency-selector__chevron{flex:none;color:var(--_text-subtle);font-size:11px;line-height:1}.fly-currency-selector__clear{padding:3px;border-radius:50%;cursor:pointer}.fly-currency-selector__clear:hover{background:var(--_surface-hover);color:var(--_text)}.fly-currency-selector__tile{display:inline-flex;flex:none;align-items:center;justify-content:center;inline-size:26px;block-size:19px;border-radius:4px;background:var(--_fill);overflow:hidden}.fly-currency-selector__tile-flag{font-size:15px;line-height:1}.fly-currency-selector__tile-code{font-size:9px;font-weight:800;letter-spacing:.04em;color:var(--_text-subtle);direction:ltr;unicode-bidi:isolate}.fly-currency-selector__locked{display:flex;align-items:center;gap:8px;inline-size:100%;min-block-size:2.25rem;padding-block:4px;padding-inline:8px;border:1px dashed var(--_border);border-radius:var(--_radius);background:var(--_fill);color:var(--_text);cursor:default}.fly-currency-selector__lock-icon{flex:none;margin-inline-start:auto;font-size:12px;opacity:.7}.fly-currency-selector__locked-reason{margin:4px 0 0;color:var(--_text-subtle);font-size:11px;line-height:1.4}.fly-currency-selector__panel{position:absolute;z-index:60;inset-inline:0;inset-block-start:calc(100% + 4px);display:flex;flex-direction:column;max-block-size:19rem;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface-panel);-webkit-backdrop-filter:blur(var(--_panel-blur));backdrop-filter:blur(var(--_panel-blur));box-shadow:0 12px 32px #0000002e;overflow:hidden}.fly-currency-selector__search{padding:6px;border-block-end:1px solid var(--_border)}.fly-currency-selector__search-input{inline-size:100%;padding-block:5px;padding-inline:8px;border:1px solid var(--_border);border-radius:6px;background:var(--_surface);color:var(--_text);font:inherit}.fly-currency-selector__search-input:focus-visible{outline:2px solid var(--_primary);outline-offset:-1px}.fly-currency-selector__listbox{flex:1 1 auto;overflow-y:auto;padding-block:4px}.fly-currency-selector__group{padding-block:6px 3px;padding-inline:10px;color:var(--_text-subtle);font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase}.fly-currency-selector__option{display:flex;align-items:center;gap:9px;padding-block:5px;padding-inline:10px;cursor:pointer}.fly-currency-selector__option--active{background:var(--_surface-hover)}.fly-currency-selector__option--selected{background:var(--_surface-active)}.fly-currency-selector__option-text{display:flex;flex:1 1 auto;align-items:baseline;gap:8px;min-inline-size:0}.fly-currency-selector__option-code{flex:none;font-weight:700;letter-spacing:.02em;direction:ltr;unicode-bidi:isolate}.fly-currency-selector__option-name{flex:1 1 auto;min-inline-size:0;color:var(--_text-subtle);overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.fly-currency-selector__option-symbol{flex:none;min-inline-size:2.2em;color:var(--_text);text-align:end}.fly-currency-selector__check{flex:none;inline-size:1em;color:var(--_primary)}.fly-currency-selector__status{display:flex;align-items:center;gap:8px;padding-block:10px;padding-inline:10px;color:var(--_text-subtle)}.fly-currency-selector__status--error{color:var(--_danger)}.fly-currency-selector__retry{border:1px solid var(--_border);border-radius:6px;background:var(--_surface);padding-block:2px;padding-inline:8px;color:var(--_text);font:inherit;font-weight:600;cursor:pointer}\n"] }]
19956
- }], ctorParameters: () => [], propDecorators: { mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], currencies: [{ type: i0.Input, args: [{ isSignal: true, alias: "currencies", required: false }] }], fetchFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "fetchFn", required: false }] }], allowedCodes: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowedCodes", required: false }] }], pinnedCodes: [{ type: i0.Input, args: [{ isSignal: true, alias: "pinnedCodes", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], locked: [{ type: i0.Input, args: [{ isSignal: true, alias: "locked", required: false }] }], lockedReasonKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "lockedReasonKey", required: false }] }], lockedReasonParams: [{ type: i0.Input, args: [{ isSignal: true, alias: "lockedReasonParams", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], selectionDetailChange: [{ type: i0.Output, args: ["selectionDetailChange"] }], openedChange: [{ type: i0.Output, args: ["openedChange"] }], searchEl: [{
20365
+ }], ctorParameters: () => [], propDecorators: { mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], currencies: [{ type: i0.Input, args: [{ isSignal: true, alias: "currencies", required: false }] }], fetchFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "fetchFn", required: false }] }], applyTenantDefault: [{ type: i0.Input, args: [{ isSignal: true, alias: "applyTenantDefault", required: false }] }], tenantDefaultFetchFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "tenantDefaultFetchFn", required: false }] }], allowedCodes: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowedCodes", required: false }] }], pinnedCodes: [{ type: i0.Input, args: [{ isSignal: true, alias: "pinnedCodes", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], locked: [{ type: i0.Input, args: [{ isSignal: true, alias: "locked", required: false }] }], lockedReasonKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "lockedReasonKey", required: false }] }], lockedReasonParams: [{ type: i0.Input, args: [{ isSignal: true, alias: "lockedReasonParams", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], selectionDetailChange: [{ type: i0.Output, args: ["selectionDetailChange"] }], openedChange: [{ type: i0.Output, args: ["openedChange"] }], searchEl: [{
19957
20366
  type: ViewChild,
19958
20367
  args: ['searchRef']
19959
20368
  }], triggerEl: [{
@@ -21811,14 +22220,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
21811
22220
  args: [{ selector: 'fly-meta', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<dl>\r\n @for (item of items(); track $index) {\r\n <div>\r\n <dt>{{ item.labelKey | translate }}</dt>\r\n <dd [class.mono]=\"item.mono ?? false\">{{ item.value }}</dd>\r\n </div>\r\n }\r\n <ng-content />\r\n</dl>\r\n", styles: [":host{display:block}dl{margin:0;display:flex;flex-direction:column;gap:10px}dl ::ng-deep>div{display:grid;grid-template-columns:96px 1fr;gap:var(--sp-3);align-items:baseline;font-size:var(--text-sm)}dl ::ng-deep dt{margin:0;font-size:10.5px;font-weight:var(--fw-medium);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-4)}dl ::ng-deep dd{margin:0;min-width:0;color:var(--ink);word-break:break-word;line-height:1.45}dl ::ng-deep dd.mono,dl ::ng-deep dd .mono{font-size:var(--text-2xs)}\n"] }]
21812
22221
  }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }] } });
21813
22222
 
21814
- function canGoPrev(page) {
22223
+ function canGoPrev$1(page) {
21815
22224
  return page > 1;
21816
22225
  }
21817
- function canGoNext(page, totalPages) {
22226
+ function canGoNext$1(page, totalPages) {
21818
22227
  return page < totalPages;
21819
22228
  }
21820
22229
  /** Clamp a requested page into `[1, max(1, totalPages)]`. */
21821
- function clampPage(page, totalPages) {
22230
+ function clampPage$1(page, totalPages) {
21822
22231
  return Math.min(Math.max(1, page), Math.max(1, totalPages));
21823
22232
  }
21824
22233
 
@@ -21853,10 +22262,10 @@ class FlyPaginationComponent {
21853
22262
  showEdges = input(false, ...(ngDevMode ? [{ debugName: "showEdges" }] : /* istanbul ignore next */ []));
21854
22263
  /** Emits the target page (already clamped to bounds). */
21855
22264
  pageChange = output();
21856
- prevEnabled = computed(() => canGoPrev(this.page()), ...(ngDevMode ? [{ debugName: "prevEnabled" }] : /* istanbul ignore next */ []));
21857
- nextEnabled = computed(() => canGoNext(this.page(), this.totalPages()), ...(ngDevMode ? [{ debugName: "nextEnabled" }] : /* istanbul ignore next */ []));
22265
+ prevEnabled = computed(() => canGoPrev$1(this.page()), ...(ngDevMode ? [{ debugName: "prevEnabled" }] : /* istanbul ignore next */ []));
22266
+ nextEnabled = computed(() => canGoNext$1(this.page(), this.totalPages()), ...(ngDevMode ? [{ debugName: "nextEnabled" }] : /* istanbul ignore next */ []));
21858
22267
  goTo(target) {
21859
- const next = clampPage(target, this.totalPages());
22268
+ const next = clampPage$1(target, this.totalPages());
21860
22269
  if (next !== this.page())
21861
22270
  this.pageChange.emit(next);
21862
22271
  }
@@ -21868,6 +22277,54 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
21868
22277
  args: [{ selector: 'fly-pagination', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<nav class=\"pg\" [attr.aria-label]=\"'ui.pagination.label' | translate\">\r\n @if (rangeText(); as range) {\r\n <span class=\"pg__range\">{{ range }}</span>\r\n }\r\n <div class=\"pg__controls\">\r\n @if (showEdges()) {\r\n <button\r\n type=\"button\"\r\n class=\"pg__btn\"\r\n [disabled]=\"!prevEnabled()\"\r\n (click)=\"goTo(1)\"\r\n [attr.aria-label]=\"'ui.pagination.first' | translate\"\r\n ><span class=\"pg__chev\" aria-hidden=\"true\">\u00AB</span></button>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"pg__btn\"\r\n [disabled]=\"!prevEnabled()\"\r\n (click)=\"goTo(page() - 1)\"\r\n [attr.aria-label]=\"'ui.pagination.previous' | translate\"\r\n >\r\n <svg class=\"pg__arrow\" data-arrow=\"prev\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <polyline points=\"15 6 9 12 15 18\" />\r\n </svg>\r\n </button>\r\n <span class=\"pg__current\" aria-live=\"polite\">\r\n {{ 'ui.pagination.pageOf' | translate: { page: page(), total: totalPages() } }}\r\n </span>\r\n <button\r\n type=\"button\"\r\n class=\"pg__btn\"\r\n [disabled]=\"!nextEnabled()\"\r\n (click)=\"goTo(page() + 1)\"\r\n [attr.aria-label]=\"'ui.pagination.next' | translate\"\r\n >\r\n <svg class=\"pg__arrow\" data-arrow=\"next\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <polyline points=\"9 6 15 12 9 18\" />\r\n </svg>\r\n </button>\r\n @if (showEdges()) {\r\n <button\r\n type=\"button\"\r\n class=\"pg__btn\"\r\n [disabled]=\"!nextEnabled()\"\r\n (click)=\"goTo(totalPages())\"\r\n [attr.aria-label]=\"'ui.pagination.last' | translate\"\r\n ><span class=\"pg__chev\" aria-hidden=\"true\">\u00BB</span></button>\r\n }\r\n </div>\r\n</nav>\r\n", styles: [".pg{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-3);margin-block-start:var(--sp-4);font-size:var(--text-sm);color:var(--ink-3)}.pg__range{font-size:var(--text-sm);color:var(--ink-3);direction:ltr;unicode-bidi:isolate}.pg__range b{color:var(--ink);font-weight:var(--fw-medium)}.pg__controls{display:flex;align-items:center;gap:2px;margin-inline-start:auto;font-size:var(--text-sm)}.pg__btn{inline-size:26px;block-size:26px;padding:0;border:1px solid transparent;background:transparent;color:var(--ink-3);border-radius:50%;font-size:var(--text-sm);cursor:pointer;display:inline-grid;place-items:center;transition:background var(--t-state),color var(--t-state)}.pg__btn:hover:not(:disabled){background:var(--w06);color:var(--ink)}.pg__btn:disabled{color:var(--w2);cursor:default}.pg__btn:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.pg__arrow{inline-size:14px;block-size:14px}:host-context([dir=rtl]) .pg__arrow{transform:scaleX(-1)}.pg__current{font-size:var(--text-sm);font-weight:var(--fw-semibold);font-variant-numeric:tabular-nums;padding:0 var(--sp-3);background:var(--accent);border:1px solid transparent;border-radius:999px;color:var(--on-accent-fill);height:24px;display:inline-grid;place-items:center;direction:ltr;unicode-bidi:isolate}:host-context(.rtl) .pg__chev,:host-context([dir=rtl]) .pg__chev{display:inline-block;transform:scaleX(-1)}\n"] }]
21869
22278
  }], propDecorators: { page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: true }] }], totalPages: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalPages", required: true }] }], rangeText: [{ type: i0.Input, args: [{ isSignal: true, alias: "rangeText", required: false }] }], showEdges: [{ type: i0.Input, args: [{ isSignal: true, alias: "showEdges", required: false }] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }] } });
21870
22279
 
22280
+ /** 0-indexed sibling of `pagination.logic.ts` — see `pager.component.ts` for why. */
22281
+ function canGoPrev(page) {
22282
+ return page > 0;
22283
+ }
22284
+ function canGoNext(page, totalPages) {
22285
+ return page < totalPages - 1;
22286
+ }
22287
+ /** Clamp a requested page into `[0, max(0, totalPages - 1)]`. */
22288
+ function clampPage(page, totalPages) {
22289
+ return Math.min(Math.max(0, page), Math.max(0, totalPages - 1));
22290
+ }
22291
+
22292
+ /**
22293
+ * Numbered list pager — back arrow, one pill per page, forward arrow — for the
22294
+ * lists that want every page as a target rather than `fly-pagination`'s
22295
+ * «prev / page of total / next» band. Shares the list's surface and continues
22296
+ * its border (see `COMPONENTS.md`): pair with a list container that sets
22297
+ * `border-bottom: 0` so the two read as one panel.
22298
+ *
22299
+ * <fly-pager [page]="page()" [totalPages]="totalPages()" (goToPage)="goTo($event)" />
22300
+ *
22301
+ * `page`/the emitted target are 0-indexed (array-index pagination); buttons
22302
+ * display `page + 1`. This is the opposite convention from `fly-pagination`
22303
+ * (1-indexed) — the two are separate components for separate list shapes, not
22304
+ * a shared base, so pick the one matching how the caller already indexes its
22305
+ * pages rather than converting at the call site.
22306
+ */
22307
+ class FlyPagerComponent {
22308
+ page = input.required(...(ngDevMode ? [{ debugName: "page" }] : /* istanbul ignore next */ []));
22309
+ totalPages = input.required(...(ngDevMode ? [{ debugName: "totalPages" }] : /* istanbul ignore next */ []));
22310
+ /** Emits the target page (already clamped to bounds), 0-indexed. */
22311
+ goToPage = output();
22312
+ prevEnabled = computed(() => canGoPrev(this.page()), ...(ngDevMode ? [{ debugName: "prevEnabled" }] : /* istanbul ignore next */ []));
22313
+ nextEnabled = computed(() => canGoNext(this.page(), this.totalPages()), ...(ngDevMode ? [{ debugName: "nextEnabled" }] : /* istanbul ignore next */ []));
22314
+ pages = computed(() => Array.from({ length: Math.max(0, this.totalPages()) }, (_, i) => i), ...(ngDevMode ? [{ debugName: "pages" }] : /* istanbul ignore next */ []));
22315
+ goTo(target) {
22316
+ const next = clampPage(target, this.totalPages());
22317
+ if (next !== this.page())
22318
+ this.goToPage.emit(next);
22319
+ }
22320
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyPagerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
22321
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyPagerComponent, isStandalone: true, selector: "fly-pager", inputs: { page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: true, transformFunction: null }, totalPages: { classPropertyName: "totalPages", publicName: "totalPages", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { goToPage: "goToPage" }, ngImport: i0, template: "@if (totalPages() > 1) {\r\n <nav class=\"pgr\" [attr.aria-label]=\"'ui.pagination.label' | translate\">\r\n <button\r\n type=\"button\"\r\n class=\"pgr__arrow\"\r\n data-arrow=\"back\"\r\n [disabled]=\"!prevEnabled()\"\r\n (click)=\"goTo(page() - 1)\"\r\n [attr.aria-label]=\"'ui.pager.previous' | translate\"\r\n >\r\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <path d=\"M14.8 5.2L8 12l6.8 6.8\" />\r\n </svg>\r\n </button>\r\n @for (p of pages(); track p) {\r\n <button\r\n type=\"button\"\r\n class=\"pgr__page\"\r\n [class.pgr__page--current]=\"p === page()\"\r\n [attr.aria-current]=\"p === page() ? 'page' : null\"\r\n (click)=\"goTo(p)\"\r\n [attr.aria-label]=\"'ui.pager.page' | translate: { page: p + 1 }\"\r\n >{{ p + 1 }}</button>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"pgr__arrow\"\r\n data-arrow=\"forward\"\r\n [disabled]=\"!nextEnabled()\"\r\n (click)=\"goTo(page() + 1)\"\r\n [attr.aria-label]=\"'ui.pager.next' | translate\"\r\n >\r\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <path d=\"M9.2 5.2L16 12l-6.8 6.8\" />\r\n </svg>\r\n </button>\r\n </nav>\r\n}\r\n", styles: [".pgr{flex:none;display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:4px;padding:14px 8px;background:var(--w03);border:1px solid var(--w08);border-top:0}.pgr__arrow{flex:none;inline-size:26px;block-size:26px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--w6);cursor:pointer;display:grid;place-items:center;transition:background var(--t-state),color var(--t-state)}.pgr__arrow svg{inline-size:14px;block-size:14px}.pgr__arrow:hover:not(:disabled){background:var(--w06)}.pgr__arrow:disabled{color:var(--w2);cursor:default}.pgr__arrow:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}:host-context([dir=rtl]) .pgr__arrow[data-arrow] svg{transform:scaleX(-1)}.pgr__page{flex:none;min-inline-size:24px;block-size:24px;padding:0 3px;border:0;border-radius:50%;background:transparent;color:var(--w45);cursor:pointer;font-family:inherit;font-size:12px;font-weight:600;line-height:1;font-variant-numeric:tabular-nums;transition:background var(--t-state),color var(--t-state)}.pgr__page:hover{background:var(--w08)}.pgr__page--current{background:var(--accent);color:var(--on-accent-fill)}.pgr__page--current:hover{background:var(--accent)}.pgr__page:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
22322
+ }
22323
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyPagerComponent, decorators: [{
22324
+ type: Component,
22325
+ args: [{ selector: 'fly-pager', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (totalPages() > 1) {\r\n <nav class=\"pgr\" [attr.aria-label]=\"'ui.pagination.label' | translate\">\r\n <button\r\n type=\"button\"\r\n class=\"pgr__arrow\"\r\n data-arrow=\"back\"\r\n [disabled]=\"!prevEnabled()\"\r\n (click)=\"goTo(page() - 1)\"\r\n [attr.aria-label]=\"'ui.pager.previous' | translate\"\r\n >\r\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <path d=\"M14.8 5.2L8 12l6.8 6.8\" />\r\n </svg>\r\n </button>\r\n @for (p of pages(); track p) {\r\n <button\r\n type=\"button\"\r\n class=\"pgr__page\"\r\n [class.pgr__page--current]=\"p === page()\"\r\n [attr.aria-current]=\"p === page() ? 'page' : null\"\r\n (click)=\"goTo(p)\"\r\n [attr.aria-label]=\"'ui.pager.page' | translate: { page: p + 1 }\"\r\n >{{ p + 1 }}</button>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"pgr__arrow\"\r\n data-arrow=\"forward\"\r\n [disabled]=\"!nextEnabled()\"\r\n (click)=\"goTo(page() + 1)\"\r\n [attr.aria-label]=\"'ui.pager.next' | translate\"\r\n >\r\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <path d=\"M9.2 5.2L16 12l-6.8 6.8\" />\r\n </svg>\r\n </button>\r\n </nav>\r\n}\r\n", styles: [".pgr{flex:none;display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:4px;padding:14px 8px;background:var(--w03);border:1px solid var(--w08);border-top:0}.pgr__arrow{flex:none;inline-size:26px;block-size:26px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--w6);cursor:pointer;display:grid;place-items:center;transition:background var(--t-state),color var(--t-state)}.pgr__arrow svg{inline-size:14px;block-size:14px}.pgr__arrow:hover:not(:disabled){background:var(--w06)}.pgr__arrow:disabled{color:var(--w2);cursor:default}.pgr__arrow:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}:host-context([dir=rtl]) .pgr__arrow[data-arrow] svg{transform:scaleX(-1)}.pgr__page{flex:none;min-inline-size:24px;block-size:24px;padding:0 3px;border:0;border-radius:50%;background:transparent;color:var(--w45);cursor:pointer;font-family:inherit;font-size:12px;font-weight:600;line-height:1;font-variant-numeric:tabular-nums;transition:background var(--t-state),color var(--t-state)}.pgr__page:hover{background:var(--w08)}.pgr__page--current{background:var(--accent);color:var(--on-accent-fill)}.pgr__page--current:hover{background:var(--accent)}.pgr__page:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}\n"] }]
22326
+ }], propDecorators: { page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: true }] }], totalPages: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalPages", required: true }] }], goToPage: [{ type: i0.Output, args: ["goToPage"] }] } });
22327
+
21871
22328
  /**
21872
22329
  * Listing card shell — one anatomy for the three reference card patterns:
21873
22330
  *
@@ -22828,11 +23285,11 @@ class PageHeaderComponent {
22828
23285
  return '#';
22829
23286
  }
22830
23287
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: PageHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
22831
- 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;padding-inline:4px;margin-inline:-4px;border-radius:6px;transition:color var(--t-state),background var(--t-state)}.crumb--link:hover{color:var(--ink);background:var(--w08)}.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:12px 32px;flex-wrap:wrap}.ph--detail .ph__row{align-items:flex-start}.ph__l{flex:1}.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-variant-numeric:tabular-nums;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 (width <= 640px){.ph__row{flex-direction:column;align-items:flex-start;gap:16px}.ph__r{align-items:flex-start;flex-flow:row wrap}.ph--detail .ph__r{flex-direction:row-reverse}.ph__title{font-size:26px}.ph__title--detail{font-size:24px}}@media(width<=640px){.ph__row{flex-direction:column;align-items:flex-start;gap:16px}.ph__r{align-items:flex-start;flex-flow:row 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 });
23288
+ 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;padding-inline:4px;margin-inline:-4px;border-radius:6px;transition:color var(--t-state),background var(--t-state)}.crumb--link:hover{color:var(--ink);background:var(--w08)}.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:12px 32px;flex-wrap:wrap}.ph--detail .ph__row{align-items:flex-start}.ph__l{flex:1}.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);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-variant-numeric:tabular-nums;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 (width <= 640px){.ph__row{flex-direction:column;align-items:flex-start;gap:16px}.ph__r{align-items:flex-start;flex-flow:row wrap}.ph--detail .ph__r{flex-direction:row-reverse}.ph__title{font-size:26px}.ph__title--detail{font-size:24px}}@media(width<=640px){.ph__row{flex-direction:column;align-items:flex-start;gap:16px}.ph__r{align-items:flex-start;flex-flow:row 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 });
22832
23289
  }
22833
23290
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: PageHeaderComponent, decorators: [{
22834
23291
  type: Component,
22835
- 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;padding-inline:4px;margin-inline:-4px;border-radius:6px;transition:color var(--t-state),background var(--t-state)}.crumb--link:hover{color:var(--ink);background:var(--w08)}.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:12px 32px;flex-wrap:wrap}.ph--detail .ph__row{align-items:flex-start}.ph__l{flex:1}.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-variant-numeric:tabular-nums;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 (width <= 640px){.ph__row{flex-direction:column;align-items:flex-start;gap:16px}.ph__r{align-items:flex-start;flex-flow:row wrap}.ph--detail .ph__r{flex-direction:row-reverse}.ph__title{font-size:26px}.ph__title--detail{font-size:24px}}@media(width<=640px){.ph__row{flex-direction:column;align-items:flex-start;gap:16px}.ph__r{align-items:flex-start;flex-flow:row wrap}.ph--detail .ph__r{flex-direction:row-reverse}.ph__title{font-size:26px}.ph__title--detail{font-size:24px}}\n"] }]
23292
+ 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;padding-inline:4px;margin-inline:-4px;border-radius:6px;transition:color var(--t-state),background var(--t-state)}.crumb--link:hover{color:var(--ink);background:var(--w08)}.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:12px 32px;flex-wrap:wrap}.ph--detail .ph__row{align-items:flex-start}.ph__l{flex:1}.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);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-variant-numeric:tabular-nums;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 (width <= 640px){.ph__row{flex-direction:column;align-items:flex-start;gap:16px}.ph__r{align-items:flex-start;flex-flow:row wrap}.ph--detail .ph__r{flex-direction:row-reverse}.ph__title{font-size:26px}.ph__title--detail{font-size:24px}}@media(width<=640px){.ph__row{flex-direction:column;align-items:flex-start;gap:16px}.ph__r{align-items:flex-start;flex-flow:row wrap}.ph--detail .ph__r{flex-direction:row-reverse}.ph__title{font-size:26px}.ph__title--detail{font-size:24px}}\n"] }]
22836
23293
  }], propDecorators: { variant: [{
22837
23294
  type: Input
22838
23295
  }], title: [{
@@ -23518,14 +23975,14 @@ class FlyAppTopbarComponent {
23518
23975
  this.rows()[index]?.nativeElement.focus();
23519
23976
  }
23520
23977
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyAppTopbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
23521
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyAppTopbarComponent, isStandalone: true, selector: "fly-app-topbar", inputs: { brandLabelKey: { classPropertyName: "brandLabelKey", publicName: "brandLabelKey", isSignal: true, isRequired: false, transformFunction: null }, sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: false, transformFunction: null }, activeKey: { classPropertyName: "activeKey", publicName: "activeKey", isSignal: true, isRequired: false, transformFunction: null }, footerLabel: { classPropertyName: "footerLabel", publicName: "footerLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { moduleSelected: "moduleSelected", brandSelected: "brandSelected" }, host: { listeners: { "document:keydown.escape": "onEscape()" }, properties: { "class.fly-topbar--docked": "docked()" } }, queries: [{ propertyName: "icons", predicate: FlyModuleIconDirective, isSignal: true }], viewQueries: [{ propertyName: "rows", predicate: ["row"], descendants: true, isSignal: true }, { propertyName: "trigger", predicate: ["trigger"], descendants: true, isSignal: true }], ngImport: i0, 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]=\"triggerAriaLabel()\"\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(--w1);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}:host(.fly-topbar--docked){flex:1 1 auto;min-inline-size:0;z-index:auto}:host(.fly-topbar--docked) .fly-topbar{padding:0;background:transparent;border-block-end:0}:host(.fly-topbar--docked) .fly-topbar__lead{gap:2px}:host(.fly-topbar--docked) .fly-topbar__brand,:host(.fly-topbar--docked) .fly-topbar__module{block-size:26px;padding-block:0;padding-inline:var(--sp-2);border-radius:var(--r-sm);font-size:var(--text-sm);line-height:1;color:var(--label-secondary)}:host(.fly-topbar--docked) .fly-topbar__brand:hover,:host(.fly-topbar--docked) .fly-topbar__module:hover{background:var(--w08);color:var(--label-primary)}:host(.fly-topbar--docked) .fly-topbar__module{color:var(--label-primary)}:host(.fly-topbar--docked) .fly-topbar__module--on{background:var(--w08)}:host(.fly-topbar--docked) .fly-topbar__brand-name{font-weight:var(--fw-medium)}:host(.fly-topbar--docked) .fly-topbar__module-label{font-weight:var(--fw-semibold)}:host(.fly-topbar--docked) .fly-topbar__sep{color:var(--w28)}:host(.fly-topbar--docked) .fly-topbar__module-icon{inline-size:14px;block-size:14px}:host(.fly-topbar--docked) .fly-topbar__brand-mark{max-block-size:22px}:host(.fly-topbar--docked) .fly-topbar__brand-mark ::ng-deep *{max-inline-size:22px;max-block-size:22px}.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-radius:var(--r-lg);border:1px solid var(--glass2-border);background-image:radial-gradient(70% 60% at 50% 100%,var(--w07),transparent 70%),linear-gradient(180deg,var(--w08),transparent 20%),linear-gradient(180deg,var(--mat-menu-solid-a),var(--mat-menu-solid-b));box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset)}@media(prefers-reduced-transparency:reduce){.fly-topbar__pop{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.fly-topbar__pop:before,.fly-topbar__pop:after{display:none}}@media(prefers-contrast:more){.fly-topbar__pop{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.fly-topbar__pop:after{animation:none}}.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(--w1)}.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:.5px solid var(--w08);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"], dependencies: [{ kind: "directive", type: FlyClickOutsideDirective, selector: "[flyClickOutside]", inputs: ["flyClickOutsideEnabled", "flyClickOutsideIgnore"], outputs: ["flyClickOutside"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
23978
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyAppTopbarComponent, isStandalone: true, selector: "fly-app-topbar", inputs: { brandLabelKey: { classPropertyName: "brandLabelKey", publicName: "brandLabelKey", isSignal: true, isRequired: false, transformFunction: null }, sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: false, transformFunction: null }, activeKey: { classPropertyName: "activeKey", publicName: "activeKey", isSignal: true, isRequired: false, transformFunction: null }, footerLabel: { classPropertyName: "footerLabel", publicName: "footerLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { moduleSelected: "moduleSelected", brandSelected: "brandSelected" }, host: { listeners: { "document:keydown.escape": "onEscape()" }, properties: { "class.fly-topbar--docked": "docked()" } }, queries: [{ propertyName: "icons", predicate: FlyModuleIconDirective, isSignal: true }], viewQueries: [{ propertyName: "rows", predicate: ["row"], descendants: true, isSignal: true }, { propertyName: "trigger", predicate: ["trigger"], descendants: true, isSignal: true }], ngImport: i0, 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]=\"triggerAriaLabel()\"\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(--w1);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}:host(.fly-topbar--docked){flex:1 1 auto;min-inline-size:0;z-index:auto}:host(.fly-topbar--docked) .fly-topbar{padding:0;background:transparent;border-block-end:0}:host(.fly-topbar--docked) .fly-topbar__lead{gap:2px}:host(.fly-topbar--docked) .fly-topbar__brand,:host(.fly-topbar--docked) .fly-topbar__module{block-size:26px;padding-block:0;padding-inline:var(--sp-2);border-radius:var(--r-md);gap:6px;font-size:var(--text-sm);line-height:1;color:var(--w55)}:host(.fly-topbar--docked) .fly-topbar__brand:hover,:host(.fly-topbar--docked) .fly-topbar__module:hover{background:var(--w08);color:var(--label-primary)}:host(.fly-topbar--docked) .fly-topbar__module{background:var(--w06);color:var(--label-primary)}:host(.fly-topbar--docked) .fly-topbar__module--on{background:var(--w08)}:host(.fly-topbar--docked) .fly-topbar__brand-name{font-weight:var(--fw-medium)}:host(.fly-topbar--docked) .fly-topbar__module-label{font-weight:var(--fw-semibold);letter-spacing:-.006em}:host-context([dir=rtl]) :host(.fly-topbar--docked) .fly-topbar__module-label{letter-spacing:normal}:host(.fly-topbar--docked) .fly-topbar__sep{position:relative;display:inline-block;inline-size:7px;block-size:7px;color:transparent;border-block-start:1.5px solid var(--w28);border-inline-end:1.5px solid var(--w28);transform:rotate(45deg)}:host-context([dir=rtl]) :host(.fly-topbar--docked) .fly-topbar__sep{transform:rotate(-135deg)}:host(.fly-topbar--docked) .fly-topbar__actions{display:none}:host(.fly-topbar--docked) .fly-topbar__module-icon{inline-size:14px;block-size:14px}:host(.fly-topbar--docked) .fly-topbar__brand-mark{max-block-size:14px}:host(.fly-topbar--docked) .fly-topbar__brand-mark ::ng-deep *{max-inline-size:14px;max-block-size:14px}.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-radius:var(--r-lg);border:1px solid var(--glass2-border);background-image:radial-gradient(70% 60% at 50% 100%,var(--w07),transparent 70%),linear-gradient(180deg,var(--w08),transparent 20%),linear-gradient(180deg,var(--mat-menu-solid-a),var(--mat-menu-solid-b));box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset)}@media(prefers-reduced-transparency:reduce){.fly-topbar__pop{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.fly-topbar__pop:before,.fly-topbar__pop:after{display:none}}@media(prefers-contrast:more){.fly-topbar__pop{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.fly-topbar__pop:after{animation:none}}.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(--w1)}.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:.5px solid var(--w08);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"], dependencies: [{ kind: "directive", type: FlyClickOutsideDirective, selector: "[flyClickOutside]", inputs: ["flyClickOutsideEnabled", "flyClickOutsideIgnore"], outputs: ["flyClickOutside"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
23522
23979
  }
23523
23980
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyAppTopbarComponent, decorators: [{
23524
23981
  type: Component,
23525
23982
  args: [{ selector: 'fly-app-topbar', standalone: true, imports: [TranslatePipe, FlyClickOutsideDirective, NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, host: {
23526
23983
  '(document:keydown.escape)': 'onEscape()',
23527
23984
  '[class.fly-topbar--docked]': 'docked()',
23528
- }, 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]=\"triggerAriaLabel()\"\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(--w1);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}:host(.fly-topbar--docked){flex:1 1 auto;min-inline-size:0;z-index:auto}:host(.fly-topbar--docked) .fly-topbar{padding:0;background:transparent;border-block-end:0}:host(.fly-topbar--docked) .fly-topbar__lead{gap:2px}:host(.fly-topbar--docked) .fly-topbar__brand,:host(.fly-topbar--docked) .fly-topbar__module{block-size:26px;padding-block:0;padding-inline:var(--sp-2);border-radius:var(--r-sm);font-size:var(--text-sm);line-height:1;color:var(--label-secondary)}:host(.fly-topbar--docked) .fly-topbar__brand:hover,:host(.fly-topbar--docked) .fly-topbar__module:hover{background:var(--w08);color:var(--label-primary)}:host(.fly-topbar--docked) .fly-topbar__module{color:var(--label-primary)}:host(.fly-topbar--docked) .fly-topbar__module--on{background:var(--w08)}:host(.fly-topbar--docked) .fly-topbar__brand-name{font-weight:var(--fw-medium)}:host(.fly-topbar--docked) .fly-topbar__module-label{font-weight:var(--fw-semibold)}:host(.fly-topbar--docked) .fly-topbar__sep{color:var(--w28)}:host(.fly-topbar--docked) .fly-topbar__module-icon{inline-size:14px;block-size:14px}:host(.fly-topbar--docked) .fly-topbar__brand-mark{max-block-size:22px}:host(.fly-topbar--docked) .fly-topbar__brand-mark ::ng-deep *{max-inline-size:22px;max-block-size:22px}.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-radius:var(--r-lg);border:1px solid var(--glass2-border);background-image:radial-gradient(70% 60% at 50% 100%,var(--w07),transparent 70%),linear-gradient(180deg,var(--w08),transparent 20%),linear-gradient(180deg,var(--mat-menu-solid-a),var(--mat-menu-solid-b));box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset)}@media(prefers-reduced-transparency:reduce){.fly-topbar__pop{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.fly-topbar__pop:before,.fly-topbar__pop:after{display:none}}@media(prefers-contrast:more){.fly-topbar__pop{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.fly-topbar__pop:after{animation:none}}.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(--w1)}.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:.5px solid var(--w08);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"] }]
23985
+ }, 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]=\"triggerAriaLabel()\"\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(--w1);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}:host(.fly-topbar--docked){flex:1 1 auto;min-inline-size:0;z-index:auto}:host(.fly-topbar--docked) .fly-topbar{padding:0;background:transparent;border-block-end:0}:host(.fly-topbar--docked) .fly-topbar__lead{gap:2px}:host(.fly-topbar--docked) .fly-topbar__brand,:host(.fly-topbar--docked) .fly-topbar__module{block-size:26px;padding-block:0;padding-inline:var(--sp-2);border-radius:var(--r-md);gap:6px;font-size:var(--text-sm);line-height:1;color:var(--w55)}:host(.fly-topbar--docked) .fly-topbar__brand:hover,:host(.fly-topbar--docked) .fly-topbar__module:hover{background:var(--w08);color:var(--label-primary)}:host(.fly-topbar--docked) .fly-topbar__module{background:var(--w06);color:var(--label-primary)}:host(.fly-topbar--docked) .fly-topbar__module--on{background:var(--w08)}:host(.fly-topbar--docked) .fly-topbar__brand-name{font-weight:var(--fw-medium)}:host(.fly-topbar--docked) .fly-topbar__module-label{font-weight:var(--fw-semibold);letter-spacing:-.006em}:host-context([dir=rtl]) :host(.fly-topbar--docked) .fly-topbar__module-label{letter-spacing:normal}:host(.fly-topbar--docked) .fly-topbar__sep{position:relative;display:inline-block;inline-size:7px;block-size:7px;color:transparent;border-block-start:1.5px solid var(--w28);border-inline-end:1.5px solid var(--w28);transform:rotate(45deg)}:host-context([dir=rtl]) :host(.fly-topbar--docked) .fly-topbar__sep{transform:rotate(-135deg)}:host(.fly-topbar--docked) .fly-topbar__actions{display:none}:host(.fly-topbar--docked) .fly-topbar__module-icon{inline-size:14px;block-size:14px}:host(.fly-topbar--docked) .fly-topbar__brand-mark{max-block-size:14px}:host(.fly-topbar--docked) .fly-topbar__brand-mark ::ng-deep *{max-inline-size:14px;max-block-size:14px}.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-radius:var(--r-lg);border:1px solid var(--glass2-border);background-image:radial-gradient(70% 60% at 50% 100%,var(--w07),transparent 70%),linear-gradient(180deg,var(--w08),transparent 20%),linear-gradient(180deg,var(--mat-menu-solid-a),var(--mat-menu-solid-b));box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset)}@media(prefers-reduced-transparency:reduce){.fly-topbar__pop{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.fly-topbar__pop:before,.fly-topbar__pop:after{display:none}}@media(prefers-contrast:more){.fly-topbar__pop{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.fly-topbar__pop:after{animation:none}}.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(--w1)}.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:.5px solid var(--w08);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"] }]
23529
23986
  }], 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 }] }] } });
23530
23987
 
23531
23988
  /**
@@ -24249,66 +24706,66 @@ class FlySegmentedComponent {
24249
24706
  this.el.nativeElement.querySelectorAll('[role="radio"]')[next]?.focus();
24250
24707
  }
24251
24708
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlySegmentedComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
24252
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlySegmentedComponent, isStandalone: true, selector: "fly-segmented", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelKey: { classPropertyName: "ariaLabelKey", publicName: "ariaLabelKey", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, values: { classPropertyName: "values", publicName: "values", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", values: "valuesChange" }, ngImport: i0, template: `
24253
- <div
24254
- class="seg"
24255
- [attr.data-variant]="variant()"
24256
- [attr.role]="multiple() ? 'group' : 'radiogroup'"
24257
- tabindex="-1"
24258
- [attr.aria-label]="ariaLabelKey() ? (ariaLabelKey()! | translate) : null"
24259
- (keydown)="onKey($event)"
24260
- >
24261
- @for (opt of options(); track opt.value) {
24262
- <button
24263
- type="button"
24264
- class="seg__btn"
24265
- [attr.role]="multiple() ? null : 'radio'"
24266
- [class.on]="isOn(opt.value)"
24267
- [attr.aria-checked]="multiple() ? null : isOn(opt.value)"
24268
- [attr.aria-pressed]="multiple() ? isOn(opt.value) : null"
24269
- [attr.tabindex]="multiple() ? null : tabIndexFor($index)"
24270
- [disabled]="disabled()"
24271
- (click)="select(opt.value)"
24272
- >
24273
- {{ opt.labelKey | translate }}
24274
- @if (opt.count !== undefined) {
24275
- <span class="seg__count">{{ opt.count }}</span>
24276
- }
24277
- </button>
24278
- }
24279
- </div>
24709
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlySegmentedComponent, isStandalone: true, selector: "fly-segmented", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelKey: { classPropertyName: "ariaLabelKey", publicName: "ariaLabelKey", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, values: { classPropertyName: "values", publicName: "values", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", values: "valuesChange" }, ngImport: i0, template: `
24710
+ <div
24711
+ class="seg"
24712
+ [attr.data-variant]="variant()"
24713
+ [attr.role]="multiple() ? 'group' : 'radiogroup'"
24714
+ tabindex="-1"
24715
+ [attr.aria-label]="ariaLabelKey() ? (ariaLabelKey()! | translate) : null"
24716
+ (keydown)="onKey($event)"
24717
+ >
24718
+ @for (opt of options(); track opt.value) {
24719
+ <button
24720
+ type="button"
24721
+ class="seg__btn"
24722
+ [attr.role]="multiple() ? null : 'radio'"
24723
+ [class.on]="isOn(opt.value)"
24724
+ [attr.aria-checked]="multiple() ? null : isOn(opt.value)"
24725
+ [attr.aria-pressed]="multiple() ? isOn(opt.value) : null"
24726
+ [attr.tabindex]="multiple() ? null : tabIndexFor($index)"
24727
+ [disabled]="disabled()"
24728
+ (click)="select(opt.value)"
24729
+ >
24730
+ {{ opt.labelKey | translate }}
24731
+ @if (opt.count !== undefined) {
24732
+ <span class="seg__count">{{ opt.count }}</span>
24733
+ }
24734
+ </button>
24735
+ }
24736
+ </div>
24280
24737
  `, isInline: true, styles: [":host{display:inline-flex}.seg{display:flex;flex-wrap:wrap;gap:3px;padding:3px;background:var(--w07);border:1px solid var(--w08);border-radius:99px}.seg[data-variant=pills]{gap:6px;padding:0;background:transparent;border:0;border-radius:0}.seg__btn{display:inline-flex;align-items:center;gap:6px;min-block-size:28px;padding:0 13px;border:0;background:transparent;color:var(--w55);font:500 12.5px/1.3 var(--font-family);font-family:inherit;border-radius:99px;cursor:pointer;white-space:nowrap;transition:background var(--nova-duration-hover) var(--nova-ease-micro),color var(--nova-duration-hover) var(--nova-ease-micro)}.seg__btn:hover{color:var(--w85)}.seg__btn:focus-visible{outline:3px solid var(--accent);outline-offset:3px}.seg__btn:disabled{opacity:.38;filter:saturate(.6);cursor:not-allowed}.seg__btn.on{background:var(--w14);color:var(--w95);font-weight:600;box-shadow:var(--shadow-card),var(--glass2-inset)}.seg__count{font-family:var(--font-mono);font-variant-numeric:tabular-nums;font-size:10.5px;padding:1px 5px;background:var(--w1);border-radius:4px;color:var(--w42)}.seg__btn.on .seg__count{color:var(--w55)}.seg[data-variant=pills] .seg__btn{min-block-size:30px;padding:0 12px;border:.5px solid var(--w12);background:var(--w04);color:var(--w7);border-radius:99px;transition:background var(--nova-duration-hover) var(--nova-ease-micro),color var(--nova-duration-hover) var(--nova-ease-micro),border-color var(--nova-duration-hover) var(--nova-ease-micro)}.seg[data-variant=pills] .seg__btn:hover:not(.on){background:var(--w08);border-color:var(--w2);color:var(--ink)}.seg[data-variant=pills] .seg__btn.on{border-color:transparent;background:var(--accent);color:var(--on-accent);font-weight:500;box-shadow:none}.seg[data-variant=pills] .seg__count{padding:2px 6px;border-radius:7px;font-family:var(--font-family);font-size:10.5px;font-weight:var(--fw-semibold);color:inherit}.seg[data-variant=pills] .seg__btn.on .seg__count{background:var(--w22);color:inherit}@media(pointer:coarse){.seg__btn,.seg[data-variant=pills] .seg__btn{min-block-size:44px}}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
24281
24738
  }
24282
24739
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlySegmentedComponent, decorators: [{
24283
24740
  type: Component,
24284
- args: [{ selector: 'fly-segmented', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: `
24285
- <div
24286
- class="seg"
24287
- [attr.data-variant]="variant()"
24288
- [attr.role]="multiple() ? 'group' : 'radiogroup'"
24289
- tabindex="-1"
24290
- [attr.aria-label]="ariaLabelKey() ? (ariaLabelKey()! | translate) : null"
24291
- (keydown)="onKey($event)"
24292
- >
24293
- @for (opt of options(); track opt.value) {
24294
- <button
24295
- type="button"
24296
- class="seg__btn"
24297
- [attr.role]="multiple() ? null : 'radio'"
24298
- [class.on]="isOn(opt.value)"
24299
- [attr.aria-checked]="multiple() ? null : isOn(opt.value)"
24300
- [attr.aria-pressed]="multiple() ? isOn(opt.value) : null"
24301
- [attr.tabindex]="multiple() ? null : tabIndexFor($index)"
24302
- [disabled]="disabled()"
24303
- (click)="select(opt.value)"
24304
- >
24305
- {{ opt.labelKey | translate }}
24306
- @if (opt.count !== undefined) {
24307
- <span class="seg__count">{{ opt.count }}</span>
24308
- }
24309
- </button>
24310
- }
24311
- </div>
24741
+ args: [{ selector: 'fly-segmented', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: `
24742
+ <div
24743
+ class="seg"
24744
+ [attr.data-variant]="variant()"
24745
+ [attr.role]="multiple() ? 'group' : 'radiogroup'"
24746
+ tabindex="-1"
24747
+ [attr.aria-label]="ariaLabelKey() ? (ariaLabelKey()! | translate) : null"
24748
+ (keydown)="onKey($event)"
24749
+ >
24750
+ @for (opt of options(); track opt.value) {
24751
+ <button
24752
+ type="button"
24753
+ class="seg__btn"
24754
+ [attr.role]="multiple() ? null : 'radio'"
24755
+ [class.on]="isOn(opt.value)"
24756
+ [attr.aria-checked]="multiple() ? null : isOn(opt.value)"
24757
+ [attr.aria-pressed]="multiple() ? isOn(opt.value) : null"
24758
+ [attr.tabindex]="multiple() ? null : tabIndexFor($index)"
24759
+ [disabled]="disabled()"
24760
+ (click)="select(opt.value)"
24761
+ >
24762
+ {{ opt.labelKey | translate }}
24763
+ @if (opt.count !== undefined) {
24764
+ <span class="seg__count">{{ opt.count }}</span>
24765
+ }
24766
+ </button>
24767
+ }
24768
+ </div>
24312
24769
  `, styles: [":host{display:inline-flex}.seg{display:flex;flex-wrap:wrap;gap:3px;padding:3px;background:var(--w07);border:1px solid var(--w08);border-radius:99px}.seg[data-variant=pills]{gap:6px;padding:0;background:transparent;border:0;border-radius:0}.seg__btn{display:inline-flex;align-items:center;gap:6px;min-block-size:28px;padding:0 13px;border:0;background:transparent;color:var(--w55);font:500 12.5px/1.3 var(--font-family);font-family:inherit;border-radius:99px;cursor:pointer;white-space:nowrap;transition:background var(--nova-duration-hover) var(--nova-ease-micro),color var(--nova-duration-hover) var(--nova-ease-micro)}.seg__btn:hover{color:var(--w85)}.seg__btn:focus-visible{outline:3px solid var(--accent);outline-offset:3px}.seg__btn:disabled{opacity:.38;filter:saturate(.6);cursor:not-allowed}.seg__btn.on{background:var(--w14);color:var(--w95);font-weight:600;box-shadow:var(--shadow-card),var(--glass2-inset)}.seg__count{font-family:var(--font-mono);font-variant-numeric:tabular-nums;font-size:10.5px;padding:1px 5px;background:var(--w1);border-radius:4px;color:var(--w42)}.seg__btn.on .seg__count{color:var(--w55)}.seg[data-variant=pills] .seg__btn{min-block-size:30px;padding:0 12px;border:.5px solid var(--w12);background:var(--w04);color:var(--w7);border-radius:99px;transition:background var(--nova-duration-hover) var(--nova-ease-micro),color var(--nova-duration-hover) var(--nova-ease-micro),border-color var(--nova-duration-hover) var(--nova-ease-micro)}.seg[data-variant=pills] .seg__btn:hover:not(.on){background:var(--w08);border-color:var(--w2);color:var(--ink)}.seg[data-variant=pills] .seg__btn.on{border-color:transparent;background:var(--accent);color:var(--on-accent);font-weight:500;box-shadow:none}.seg[data-variant=pills] .seg__count{padding:2px 6px;border-radius:7px;font-family:var(--font-family);font-size:10.5px;font-weight:var(--fw-semibold);color:inherit}.seg[data-variant=pills] .seg__btn.on .seg__count{background:var(--w22);color:inherit}@media(pointer:coarse){.seg__btn,.seg[data-variant=pills] .seg__btn{min-block-size:44px}}\n"] }]
24313
24770
  }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], ariaLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelKey", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], values: [{ type: i0.Input, args: [{ isSignal: true, alias: "values", required: false }] }, { type: i0.Output, args: ["valuesChange"] }] } });
24314
24771
 
@@ -26067,5 +26524,5 @@ const AUDIENCE_ERROR_CODES = {
26067
26524
  * Generated bundle index. Do not edit.
26068
26525
  */
26069
26526
 
26070
- export { AGENT_DRAG_MIME, AGENT_PAYLOAD_VERSION, APP_LOOKUP, AUDIENCE_ERROR_CODES, AUDIENCE_LIMITS, AUDIENCE_PRESETS, AUDIENCE_TERM_KINDS, AgentActionBus, AgentActionUnsupportedDispatchError, AgentCommandRegistry, AgentDropRegistry, AgentFlightAnimator, AgentLookupRegistry, AgentPayloadOversizeError, AudienceBuilderComponent, AuthService, BilingualFieldComponent, CRON_MODES, CRON_WEEKDAYS, ContextMenuComponent, DEFAULT_AGENT_PAYLOAD_LIMITS, DEFAULT_FLY_THEME_MODE, DS_BASELINE_LOCALES, DashboardKpiComponent, DialogResult, ENTITY_LINK_LAUNCHER, EntityLookupComponent, FLYOS_LAUNCH_EVENT, FLYOS_LAUNCH_REQUEST_EVENT, FLYOS_REMOTE_ROUTE_EVENT, FLY_ACCENT_PROPERTY, FLY_ADMIN_ROLES, FLY_CHUNK_RELOAD_FLAG, FLY_COMPACT_THRESHOLD, FLY_COUNTRIES, FLY_CURRENCIES_BRIEF_ENDPOINT, FLY_EMOJI_BY_ID, FLY_EMOJI_CATEGORIES, FLY_EMOJI_CATEGORY_BY_ID, FLY_EMOJI_DEFAULT_CATEGORIES, FLY_EMOJI_PACK, FLY_EMOJI_PACK_MISSING_MESSAGE, FLY_EMOJI_PREVIEW_COUNT, FLY_EMPTY_VALUE, FLY_LOCALE_CATALOG, FLY_MAGIC_BAR_EVENT, FLY_MAGIC_BAR_ICONS, FLY_MAGIC_BAR_STORE_KEY, FLY_NF_CACHE_HEAL_FLAG, FLY_RELOAD_DEBOUNCE_MS, FLY_RELOAD_REARM_MS, FLY_REMOTE_BASE_PATH, FLY_REMOTE_CONTEXT_EVENT, FLY_REMOTE_CONTEXT_STORE_KEY, FLY_REMOTE_ROUTES, FLY_SCAN_PENDING_DEFAULT_DELAY_MS, FLY_SCAN_PENDING_MAX_RETRIES, FLY_SCAN_PENDING_STATUS, FLY_SEARCH_DEBOUNCE_MS, FLY_SKIN_TONES, FLY_STANDALONE_AUTH_CONFIG, FLY_THEME_MODE_IDS, FLY_VIEWPORT_IS_MOBILE, FLY_WINDOW_HELP_HINT_EVENT, FLY_WINDOW_PAGE_TITLE_EVENT, FlyAchievementProgressComponent, FlyActionMenuComponent, FlyActionStackComponent, FlyAgentDraggableDirective, FlyAnimatedEmojiComponent, FlyAppHomeComponent, FlyAppTopbarComponent, FlyAppUnavailableComponent, FlyAuthLiveRefresh, FlyBadgeWallComponent, FlyBlockUiComponent, FlyBreadcrumbComponent, FlyButtonComponent, FlyBytesPipe, FlyCaptchaComponent, FlyCardActionsComponent, FlyCardBodyComponent, FlyCardComponent, FlyCardFooterComponent, FlyCardGridComponent, FlyCardMediaComponent, FlyCardMetaComponent, FlyCardStatusComponent, FlyCardTitleComponent, FlyCellDirective, FlyCheckboxComponent, FlyChipComponent, FlyChunkReloadErrorHandler, FlyClickOutsideDirective, FlyCollapsibleComponent, FlyCommentThreadComponent, FlyCompactNumberPipe, FlyConfirmDialogComponent, FlyControlDirective, FlyCronBuilderComponent, FlyCurrencyCatalogService, FlyCurrencySelectorComponent, FlyDataTableComponent, FlyDatePipe, FlyDateTimePipe, FlyDebouncer, FlyDecimalNumberPipe, FlyDeepLinkPrefetchService, FlyDetailCardComponent, FlyDetailShellComponent, FlyDrawerComponent, FlyDurationPipe, FlyDynamicFormComponent, FlyEmojiPickerComponent, FlyFieldComponent, FlyFileDownloadService, FlyFileUploadComponent, FlyFilterPanelComponent, FlyFormBannerComponent, FlyFormFooterComponent, FlyFormGridComponent, FlyFormSectionComponent, FlyFullNumberPipe, FlyGanttComponent, FlyHubClient, FlyIconButtonComponent, FlyIfAdminDirective, FlyIfRoleDirective, FlyImageUploadComponent, FlyLeaderboardComponent, FlyMagicActionsComponent, FlyMetaListComponent, FlyModalComponent, FlyModerationQueueComponent, FlyModuleIconDirective, FlyMoneyPipe, FlyPaginationComponent, FlyPeoplePickerComponent, FlyPointsTierComponent, FlyProgressComponent, FlyRelativeTimePipe, FlyRemoteContextService, FlyRemoteRouter, FlyRemoteRouterOutletComponent, FlySearchInputComponent, FlySectionHeaderComponent, FlySecureSrcDirective, FlySegmentedComponent, FlySelectComponent, FlySkeletonComponent, FlySliderComponent, FlySparklineComponent, FlySpinnerComponent, FlyStandaloneAuthCallbackComponent, FlyStandaloneAuthService, FlyStandaloneMagicActionsComponent, FlyStateMessageComponent, FlyDynamicFormComponent as FlySurveyFormComponent, FlyTabComponent, FlyTabsComponent, FlyTagsInputComponent, FlyThemeService, FlyTimePipe, FlyToastService, FlyToggleComponent, FlyTooltipDirective, FlyTreeNavComponent, FlyTypeaheadComponent, FlyUserDirectoryService, FlyWindowHelpService, FlyWindowTitleService, FlyosPendingLaunchesGlobalKey, FlyosShellHandlesLaunchRequestsGlobalKey, FlyosShellOwnsHistoryGlobalKey, GANTT_DEPENDENCY_TYPES, GANTT_ROW_TINT_ALPHA, GANTT_ZOOMS, I18nService, LAUNCH_CONTEXT, MAGIC_BAR_SEARCH_WIDTH_DEFAULT, MAGIC_BAR_SEARCH_WIDTH_MAX, MAGIC_BAR_SEARCH_WIDTH_MIN, MagicBarRegistry, MessageBoxButtons, MessageBoxComponent, MessageBoxIcon, MessageBoxService, MockAuthService, NOVA_PRIORITY_TONE, NOVA_STATUS_TONE, NOVA_TONE_FALLBACK, OverlayStack, PRESENCE_COLORS, PageHeaderComponent, RTL_LOCALE_SET, SHARE_ORG_CHART_SYSTEM_KEY_APPS, SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT, SHARE_PANEL_DEFAULT_FILE_LEVELS, STATE_DEFAULT_ICONS, STATE_DEFAULT_MESSAGE_KEYS, STEP_UP_CODE, STEP_UP_REAUTH_HANDLER, STEP_UP_RETURN_KEY, SUPPORTED_AGENT_PAYLOAD_VERSIONS, SharePanelComponent, SourceAppResolver, StandaloneWindowManagerService, StatusBadgeComponent, StepUpService, ToastHostComponent, TranslatePipe, WINDOW_DATA, WINDOW_HELP_HINT, WindowManagerService, applySkinTone, applySuggestion, ariaSort, buildCron, canConfirm, canGoNext, canGoPrev, captureFocus, clampMagicBarSearchWidth, clampPage, clampSliderValue, connectRemoteLaunch, enterActivatesNatively, filterGroup, filterSuggestions, findLocaleByDialect, findLocaleByPrefix, firstEnabledIndex, firstModuleIndex, flattenModules, flyAdminGuard, flyApiErrorMessage, flyCaptchaBase64Utf8, flyCaptchaBuildToken, flyCaptchaSha256Hex, flyCompactNumber, flyDebounced, flyDecimalNumber, flyDownloadBlob, flyDuration, flyEmojiFor, flyEmojiIsTonable, flyEmojiPackUrl, flyExportFileName, flyFirstEnabledIndex, flyFormatBytes, flyFormatDate, flyFormatDateTime, flyFormatMoney, flyFormatTime, flyFullNumber, flyNextEnabledIndex, flyNextSegmentIndex, flyRelativeTime, flyResolveActiveTab, flyRoleGuard, flyScanPendingDelayMs, flyScanRetry, flySignedCompact, flySkinToneLabelKey, flyStandaloneAuthGuard, flyStandaloneAuthInterceptor, flyToDateOnly, flyToPage, flyUnwrap, flyUnwrapCurrencies, flyUnwrapLenient, hasAnyRoleIn, healNativeFederationCacheOnce, initialExpanded, isAccentColor, isChunkLoadError, isFlyAuthStorePopulated, isNativeFederationCacheError, isRtlLocale, isRtlLocaleEntry, isValidCron, isValidSingleField, loadRemoteStyles, magicBarOwnerKey, magicBarSearchText, matchFlyRoutePattern, nextEnabledIndex, nextModuleIndex, nextSegmentIndex, nextSort, normalizeFlyTheme, normalizeRoles, novaPriorityToneVar, novaStatusToneVar, overlayStack, parseCron, parseStepUpChallenge, parseTags, prefetchRemoteStyles, presenceColorFor, printConsoleSecurityWarning, provideFlyChunkReloadRecovery, provideFlyEmojiPack, provideFlyStandaloneAuth, reloadOnceForChunkError, requestAppLaunch, resolveActiveModule, resolveActiveSection, resolveActiveTab, resolveCellValue, resolveStateIcon, resolveStateMessageKey, restoreFocus, sameTags, sectionHintKey, sectionLayout, sliderTrackGradient, sparklinePath, sparklinePoints, stepUpInterceptor, stepUpReturnKey, trimAgentPayload, trimAgentString, unloadRemoteStyles, utf8ByteLength, validateAgentPayload, warnIfEmbeddedSessionMissing };
26527
+ export { AGENT_DRAG_MIME, AGENT_PAYLOAD_VERSION, APP_LOOKUP, AUDIENCE_ERROR_CODES, AUDIENCE_LIMITS, AUDIENCE_PRESETS, AUDIENCE_TERM_KINDS, AgentActionBus, AgentActionUnsupportedDispatchError, AgentCommandRegistry, AgentDropRegistry, AgentFlightAnimator, AgentLookupRegistry, AgentPayloadOversizeError, AudienceBuilderComponent, AuthService, BilingualFieldComponent, CRON_MODES, CRON_WEEKDAYS, ContextMenuComponent, DEFAULT_AGENT_PAYLOAD_LIMITS, DEFAULT_FLY_THEME_MODE, DS_BASELINE_LOCALES, DashboardKpiComponent, DialogResult, ENTITY_LINK_LAUNCHER, EntityLookupComponent, FLYOS_LAUNCH_EVENT, FLYOS_LAUNCH_REQUEST_EVENT, FLYOS_REMOTE_ROUTE_EVENT, FLY_ACCENT_PROPERTY, FLY_ADMIN_ROLES, FLY_CHUNK_RELOAD_FLAG, FLY_COMPACT_THRESHOLD, FLY_COUNTRIES, FLY_CURRENCIES_BRIEF_ENDPOINT, FLY_CURRENCY_DEFAULT_ENDPOINT, FLY_EMOJI_BY_ID, FLY_EMOJI_CATEGORIES, FLY_EMOJI_CATEGORY_BY_ID, FLY_EMOJI_DEFAULT_CATEGORIES, FLY_EMOJI_PACK, FLY_EMOJI_PACK_MISSING_MESSAGE, FLY_EMOJI_PREVIEW_COUNT, FLY_EMPTY_VALUE, FLY_LOCALE_CATALOG, FLY_MAGIC_BAR_EVENT, FLY_MAGIC_BAR_ICONS, FLY_MAGIC_BAR_STORE_KEY, FLY_NF_CACHE_HEAL_FLAG, FLY_RELOAD_DEBOUNCE_MS, FLY_RELOAD_REARM_MS, FLY_REMOTE_BASE_PATH, FLY_REMOTE_CONTEXT_EVENT, FLY_REMOTE_CONTEXT_STORE_KEY, FLY_REMOTE_ROUTES, FLY_SCAN_PENDING_DEFAULT_DELAY_MS, FLY_SCAN_PENDING_MAX_RETRIES, FLY_SCAN_PENDING_STATUS, FLY_SEARCH_DEBOUNCE_MS, FLY_SKIN_TONES, FLY_STANDALONE_AUTH_CONFIG, FLY_THEME_MODE_IDS, FLY_VIEWPORT_IS_MOBILE, FLY_WINDOW_BREADCRUMBS_EVENT, FLY_WINDOW_BREADCRUMBS_STORE_KEY, FLY_WINDOW_HELP_HINT_EVENT, FLY_WINDOW_PAGE_TITLE_EVENT, FlyAchievementProgressComponent, FlyActionMenuComponent, FlyActionStackComponent, FlyAgentDraggableDirective, FlyAnimatedEmojiComponent, FlyAppHomeComponent, FlyAppTopbarComponent, FlyAppUnavailableComponent, FlyAuthLiveRefresh, FlyBadgeWallComponent, FlyBlockUiComponent, FlyBreadcrumbComponent, FlyButtonComponent, FlyBytesPipe, FlyCaptchaComponent, FlyCardActionsComponent, FlyCardBodyComponent, FlyCardComponent, FlyCardFooterComponent, FlyCardGridComponent, FlyCardMediaComponent, FlyCardMetaComponent, FlyCardStatusComponent, FlyCardTitleComponent, FlyCellDirective, FlyCheckboxComponent, FlyChipComponent, FlyChunkReloadErrorHandler, FlyClickOutsideDirective, FlyCollapsibleComponent, FlyCommentThreadComponent, FlyCompactNumberPipe, FlyConfirmDialogComponent, FlyControlDirective, FlyCronBuilderComponent, FlyCurrencyCatalogService, FlyCurrencySelectorComponent, FlyDataTableComponent, FlyDatePipe, FlyDateTimePipe, FlyDebouncer, FlyDecimalNumberPipe, FlyDeepLinkPrefetchService, FlyDetailCardComponent, FlyDetailShellComponent, FlyDrawerComponent, FlyDurationPipe, FlyDynamicFormComponent, FlyEmojiPickerComponent, FlyFieldComponent, FlyFileDownloadService, FlyFileUploadComponent, FlyFilterPanelComponent, FlyFormBannerComponent, FlyFormFooterComponent, FlyFormGridComponent, FlyFormSectionComponent, FlyFullNumberPipe, FlyGanttComponent, FlyHubClient, FlyIconButtonComponent, FlyIfAdminDirective, FlyIfRoleDirective, FlyImageUploadComponent, FlyLeaderboardComponent, FlyMagicActionsComponent, FlyMetaListComponent, FlyModalComponent, FlyModerationQueueComponent, FlyModuleIconDirective, FlyMoneyPipe, FlyPagerComponent, FlyPaginationComponent, FlyPeoplePickerComponent, FlyPointsTierComponent, FlyProgressComponent, FlyRelativeTimePipe, FlyRemoteContextService, FlyRemoteRouter, FlyRemoteRouterOutletComponent, FlySearchInputComponent, FlySectionHeaderComponent, FlySecureSrcDirective, FlySegmentedComponent, FlySelectComponent, FlySkeletonComponent, FlySliderComponent, FlySparklineComponent, FlySpinnerComponent, FlyStandaloneAuthCallbackComponent, FlyStandaloneAuthService, FlyStandaloneMagicActionsComponent, FlyStateMessageComponent, FlyDynamicFormComponent as FlySurveyFormComponent, FlyTabComponent, FlyTabsComponent, FlyTagsInputComponent, FlyThemeService, FlyTimePipe, FlyToastService, FlyToggleComponent, FlyTooltipDirective, FlyTreeNavComponent, FlyTypeaheadComponent, FlyUserDirectoryService, FlyWindowHelpService, FlyWindowTitleService, FlyosPendingLaunchesGlobalKey, FlyosShellHandlesLaunchRequestsGlobalKey, FlyosShellOwnsHistoryGlobalKey, GANTT_DEPENDENCY_TYPES, GANTT_ROW_TINT_ALPHA, GANTT_ZOOMS, I18nService, LAUNCH_CONTEXT, MAGIC_BAR_SEARCH_WIDTH_DEFAULT, MAGIC_BAR_SEARCH_WIDTH_MAX, MAGIC_BAR_SEARCH_WIDTH_MIN, MagicBarRegistry, MessageBoxButtons, MessageBoxComponent, MessageBoxIcon, MessageBoxService, MockAuthService, NOVA_PRIORITY_TONE, NOVA_STATUS_TONE, NOVA_TONE_FALLBACK, OverlayStack, PRESENCE_COLORS, PageHeaderComponent, RTL_LOCALE_SET, SHARE_ORG_CHART_SYSTEM_KEY_APPS, SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT, SHARE_PANEL_DEFAULT_FILE_LEVELS, STATE_DEFAULT_ICONS, STATE_DEFAULT_MESSAGE_KEYS, STEP_UP_CODE, STEP_UP_REAUTH_HANDLER, STEP_UP_RETURN_KEY, SUPPORTED_AGENT_PAYLOAD_VERSIONS, SharePanelComponent, SourceAppResolver, StandaloneWindowManagerService, StatusBadgeComponent, StepUpService, ToastHostComponent, TranslatePipe, WINDOW_DATA, WINDOW_HELP_HINT, WindowBreadcrumbsRegistry, WindowManagerService, applySkinTone, applySuggestion, ariaSort, buildCron, canConfirm, canGoNext$1 as canGoNext, canGoPrev$1 as canGoPrev, captureFocus, clampMagicBarSearchWidth, clampPage$1 as clampPage, clampSliderValue, connectRemoteLaunch, enterActivatesNatively, filterGroup, filterSuggestions, findLocaleByDialect, findLocaleByPrefix, firstEnabledIndex, firstModuleIndex, flattenModules, flyAdminGuard, flyApiErrorMessage, flyCaptchaBase64Utf8, flyCaptchaBuildToken, flyCaptchaSha256Hex, flyCompactNumber, flyDebounced, flyDecimalNumber, flyDownloadBlob, flyDuration, flyEmojiFor, flyEmojiIsTonable, flyEmojiPackUrl, flyExportFileName, flyFirstEnabledIndex, flyFormatBytes, flyFormatDate, flyFormatDateTime, flyFormatMoney, flyFormatTime, flyFullNumber, flyNextEnabledIndex, flyNextSegmentIndex, flyRelativeTime, flyResolveActiveTab, flyRoleGuard, flyScanPendingDelayMs, flyScanRetry, flySignedCompact, flySkinToneLabelKey, flyStandaloneAuthGuard, flyStandaloneAuthInterceptor, flyToDateOnly, flyToPage, flyUnwrap, flyUnwrapCurrencies, flyUnwrapCurrency, flyUnwrapLenient, hasAnyRoleIn, healNativeFederationCacheOnce, initialExpanded, isAccentColor, isChunkLoadError, isFlyAuthStorePopulated, isNativeFederationCacheError, isRtlLocale, isRtlLocaleEntry, isValidCron, isValidSingleField, loadRemoteStyles, magicBarOwnerKey, magicBarSearchText, matchFlyRoutePattern, nextEnabledIndex, nextModuleIndex, nextSegmentIndex, nextSort, normalizeFlyTheme, normalizeRoles, novaPriorityToneVar, novaStatusToneVar, overlayStack, canGoNext as pagerCanGoNext, canGoPrev as pagerCanGoPrev, clampPage as pagerClampPage, parseCron, parseStepUpChallenge, parseTags, prefetchRemoteStyles, presenceColorFor, printConsoleSecurityWarning, provideFlyChunkReloadRecovery, provideFlyEmojiPack, provideFlyStandaloneAuth, reloadOnceForChunkError, requestAppLaunch, resolveActiveModule, resolveActiveSection, resolveActiveTab, resolveCellValue, resolveStateIcon, resolveStateMessageKey, restoreFocus, sameTags, sectionHintKey, sectionLayout, sliderTrackGradient, sparklinePath, sparklinePoints, stepUpInterceptor, stepUpReturnKey, trimAgentPayload, trimAgentString, unloadRemoteStyles, utf8ByteLength, validateAgentPayload, warnIfEmbeddedSessionMissing };
26071
26528
  //# sourceMappingURL=flyos-design-system.mjs.map