@flyos/design-system 1.8.0 → 2.1.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.
@@ -48,7 +48,7 @@ import { CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
48
48
  // tools/publish-library.ps1 at bump time, and asserted by the spec beside this file.
49
49
  // Used only for the diagnostic message; the duplicate-instance detection itself is
50
50
  // version-agnostic, so a stale literal misnames a fork rather than hiding one.
51
- const FLY_DS_VERSION = '1.8.0';
51
+ const FLY_DS_VERSION = '2.1.0';
52
52
  const FLY_DS_REGISTRY_KEY = '__FLY_DS_INSTANCES__';
53
53
  /**
54
54
  * Records this design-system instance on the shared `scope` and returns the
@@ -1594,6 +1594,7 @@ const DS_BASELINE_LOCALES = {
1594
1594
  'currency_selector.clear': 'Clear selection',
1595
1595
  'currency_selector.pinned': 'Frequently used',
1596
1596
  'currency_selector.all': 'All currencies',
1597
+ 'currency_selector.locked_default_reason': 'This currency is locked and can’t be changed.',
1597
1598
  // chat composer (fly-chat-composer)
1598
1599
  'chat_composer.label.message': 'Message',
1599
1600
  'chat_composer.label.mention_suggestions': 'Mention suggestions',
@@ -1957,6 +1958,7 @@ const DS_BASELINE_LOCALES = {
1957
1958
  'currency_selector.clear': 'مسح التحديد',
1958
1959
  'currency_selector.pinned': 'الأكثر استخداماً',
1959
1960
  'currency_selector.all': 'كل العملات',
1961
+ 'currency_selector.locked_default_reason': 'هذه العملة مقفلة ولا يمكن تغييرها.',
1960
1962
  // chat composer (fly-chat-composer)
1961
1963
  'chat_composer.label.message': 'رسالة',
1962
1964
  'chat_composer.label.mention_suggestions': 'اقتراحات الإشارة',
@@ -2320,6 +2322,7 @@ const DS_BASELINE_LOCALES = {
2320
2322
  'currency_selector.clear': 'Effacer la sélection',
2321
2323
  'currency_selector.pinned': 'Fréquemment utilisées',
2322
2324
  'currency_selector.all': 'Toutes les devises',
2325
+ 'currency_selector.locked_default_reason': 'Cette devise est verrouillée et ne peut pas être modifiée.',
2323
2326
  // chat composer (fly-chat-composer)
2324
2327
  'chat_composer.label.message': 'Message',
2325
2328
  'chat_composer.label.mention_suggestions': 'Suggestions de mention',
@@ -2682,6 +2685,7 @@ const DS_BASELINE_LOCALES = {
2682
2685
  'currency_selector.clear': 'انتخاب صاف کریں',
2683
2686
  'currency_selector.pinned': 'کثرت سے استعمال شدہ',
2684
2687
  'currency_selector.all': 'تمام کرنسیاں',
2688
+ 'currency_selector.locked_default_reason': 'یہ کرنسی مقفل ہے اور اسے تبدیل نہیں کیا جا سکتا۔',
2685
2689
  // chat composer (fly-chat-composer)
2686
2690
  'chat_composer.label.message': 'پیغام',
2687
2691
  'chat_composer.label.mention_suggestions': 'تذکرے کی تجاویز',
@@ -4582,6 +4586,22 @@ const _linkLayerSupported = typeof HTMLLinkElement !== 'undefined' && 'layer' in
4582
4586
  const _cspNonce = FLY_CSP_NONCE;
4583
4587
  /** In-flight fetch promises keyed by appId — prevents duplicate fetches. */
4584
4588
  const _inFlight = new Map();
4589
+ /** Upper bound on the optional bytes-applied wait — never holds a caller hostage. */
4590
+ const APPLY_CAP_MS = 4000;
4591
+ /** Resolves when `el` fires load/error, or after `capMs` — whichever is first. */
4592
+ function _awaitLoadCapped(el, capMs) {
4593
+ return new Promise((resolve) => {
4594
+ const timer = setTimeout(done, capMs);
4595
+ function done() {
4596
+ clearTimeout(timer);
4597
+ el.removeEventListener('load', done);
4598
+ el.removeEventListener('error', done);
4599
+ resolve();
4600
+ }
4601
+ el.addEventListener('load', done);
4602
+ el.addEventListener('error', done);
4603
+ });
4604
+ }
4585
4605
  // ---------------------------------------------------------------------------
4586
4606
  // Layer order — injected immediately at module init (synchronous, runs once).
4587
4607
  // ---------------------------------------------------------------------------
@@ -4748,7 +4768,7 @@ function _validateHref(href, remoteBaseUrl) {
4748
4768
  * The `data-fly-href` attribute stores the discovered href separately from the
4749
4769
  * element content so idempotency checks can compare the URL without parsing CSS.
4750
4770
  */
4751
- async function loadRemoteStyles(appId, remoteBaseUrl) {
4771
+ async function loadRemoteStyles(appId, remoteBaseUrl, opts = {}) {
4752
4772
  if (typeof document === 'undefined')
4753
4773
  return; // SSR guard
4754
4774
  // Resolve relative remoteBaseUrl (e.g. '/circles-dev') against the current
@@ -4767,7 +4787,9 @@ async function loadRemoteStyles(appId, remoteBaseUrl) {
4767
4787
  const href = await fetchPromise;
4768
4788
  _inFlight.delete(appId);
4769
4789
  if (!href) {
4770
- console.warn(`[FlyOS] loadRemoteStyles: no stylesheet found in ${absoluteBase}/index.html for appId="${appId}"`);
4790
+ if (!opts.silent) {
4791
+ console.warn(`[FlyOS] loadRemoteStyles: no stylesheet found in ${absoluteBase}/index.html for appId="${appId}"`);
4792
+ }
4771
4793
  return;
4772
4794
  }
4773
4795
  // Check both <link> and <style> selectors — handle upgrades from the old fallback path.
@@ -4792,19 +4814,43 @@ async function loadRemoteStyles(appId, remoteBaseUrl) {
4792
4814
  if (_cspNonce)
4793
4815
  link.nonce = _cspNonce;
4794
4816
  document.head.appendChild(link);
4817
+ // A stylesheet <link> fires load once its bytes are parsed — the direct signal.
4818
+ if (opts.awaitApplied)
4819
+ await _awaitLoadCapped(link, APPLY_CAP_MS);
4795
4820
  }
4796
4821
  else {
4797
4822
  // Fallback path: <style> with @import layer(remote) for older browsers.
4798
4823
  // CSS Cascade Level 5: `@import url("…") layer(remote)` is the only valid
4799
4824
  // way to place an @import inside a named cascade layer. Block-form
4800
4825
  // `@layer remote { @import … }` is invalid and silently dropped by browsers.
4826
+ //
4801
4827
  const style = document.createElement('style');
4802
4828
  style.setAttribute('data-fly-app', appId);
4803
4829
  style.setAttribute('data-fly-href', href);
4804
4830
  style.textContent = `@import url("${href}") layer(remote);`;
4805
4831
  if (_cspNonce)
4806
4832
  style.nonce = _cspNonce;
4833
+ // Appended synchronously after the `existing` check above — an `await`
4834
+ // between check and append would let a concurrent caller double-inject.
4807
4835
  document.head.appendChild(style);
4836
+ // A <style> element fires no load event for its @import, so for the
4837
+ // bytes-applied guarantee we shadow the same href with a `<link
4838
+ // rel="preload" as="style">`: it rides the @import's own in-flight fetch
4839
+ // (same no-cors request key) and its load event is the byte signal.
4840
+ // Deliberately NO crossorigin attribute — the @import request is no-cors,
4841
+ // and a CORS-mode preload would create a non-matching cache entry
4842
+ // (double fetch).
4843
+ if (opts.awaitApplied) {
4844
+ const preload = document.createElement('link');
4845
+ preload.setAttribute('rel', 'preload');
4846
+ preload.setAttribute('as', 'style');
4847
+ preload.setAttribute('href', href);
4848
+ if (_cspNonce)
4849
+ preload.nonce = _cspNonce;
4850
+ document.head.appendChild(preload);
4851
+ await _awaitLoadCapped(preload, APPLY_CAP_MS);
4852
+ preload.remove();
4853
+ }
4808
4854
  }
4809
4855
  }
4810
4856
  /**
@@ -5040,11 +5086,7 @@ class MockAuthService {
5040
5086
  // All signal/computed calls are inside the constructor body so Angular's
5041
5087
  // injection context and ngDevMode are fully set up before they execute.
5042
5088
  this._config = this.getConfig();
5043
- this._session = signal({
5044
- accessToken: this._config.token ?? 'mock-token',
5045
- user: this._config.user,
5046
- expiresAt: Date.now() + 24 * 60 * 60 * 1000,
5047
- }, ...(ngDevMode ? [{ debugName: "_session" }] : /* istanbul ignore next */ []));
5089
+ this._session = signal(this.createSession(), ...(ngDevMode ? [{ debugName: "_session" }] : /* istanbul ignore next */ []));
5048
5090
  this.isAuthenticated = computed(() => {
5049
5091
  const s = this._session();
5050
5092
  return s !== null && s.expiresAt > Date.now();
@@ -5068,6 +5110,14 @@ class MockAuthService {
5068
5110
  return;
5069
5111
  this._session.set({ ...session, user: { ...session.user, ...patch } });
5070
5112
  }
5113
+ /** A fresh 24h mock session from the app-supplied config — used at construction and by {@link startLogin}. */
5114
+ createSession() {
5115
+ return {
5116
+ accessToken: this._config.token ?? 'mock-token',
5117
+ user: this._config.user,
5118
+ expiresAt: Date.now() + 24 * 60 * 60 * 1000,
5119
+ };
5120
+ }
5071
5121
  /** Override in subclass to supply app-specific mock data. */
5072
5122
  getConfig() {
5073
5123
  return {
@@ -5088,6 +5138,15 @@ class MockAuthService {
5088
5138
  // Mock mode: already authenticated, nothing to initialize.
5089
5139
  }
5090
5140
  startLogin() {
5141
+ // Parity with a real STS round-trip: startLogin() must land the user back
5142
+ // AUTHENTICATED. After logout() nulls the session, a guard-triggered
5143
+ // startLogin() that only navigated would bounce off authGuard forever
5144
+ // (navigate → guard sees no session → startLogin → navigate → …) and the
5145
+ // login CTA would appear dead. Re-arming the session first makes every
5146
+ // mock login an instant, self-healing "STS visit".
5147
+ if (this._session() === null) {
5148
+ this._session.set(this.createSession());
5149
+ }
5091
5150
  this.router.navigate(this._config.loginRedirect ?? ['/']);
5092
5151
  }
5093
5152
  handleCallback(_code, _state) {
@@ -7979,11 +8038,11 @@ class ContextMenuComponent {
7979
8038
  this.previouslyFocused = null;
7980
8039
  }
7981
8040
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: ContextMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7982
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: ContextMenuComponent, isStandalone: true, selector: "fly-context-menu", inputs: { x: { classPropertyName: "x", publicName: "x", isSignal: true, isRequired: false, transformFunction: null }, y: { classPropertyName: "y", publicName: "y", isSignal: true, isRequired: false, transformFunction: null }, anchor: { classPropertyName: "anchor", publicName: "anchor", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: true, transformFunction: null }, boundary: { classPropertyName: "boundary", publicName: "boundary", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { action: "action", closed: "closed" }, host: { listeners: { "document:mousedown": "onClickOutside($event)", "document:keydown.escape": "onEscape()", "document:contextmenu": "onContextMenu($event)", "document:keydown": "onKeydown($event)" } }, viewQueries: [{ propertyName: "menuEl", first: true, predicate: ["contextMenu"], descendants: true }, { propertyName: "childMenuRef", first: true, predicate: ["childMenu"], descendants: true }], ngImport: i0, template: "<div\r\n #contextMenu\r\n class=\"context-menu\"\r\n [style.left.px]=\"clampedPos().left\"\r\n [style.top.px]=\"clampedPos().top\"\r\n role=\"menu\"\r\n [attr.aria-label]=\"'shell.context_menu' | translate\">\r\n\r\n @for (section of sections(); track $index) {\r\n @if ($index > 0) {\r\n <div class=\"menu-divider\"></div>\r\n }\r\n @if (section.label) {\r\n <div class=\"menu-section-label\">{{ section.label }}</div>\r\n }\r\n @for (item of section.items; track item.id) {\r\n <button\r\n type=\"button\"\r\n class=\"fos-btn sm rect menu-item\"\r\n [class.menu-item--has-children]=\"!!item.children?.length\"\r\n [class.menu-item--selected]=\"item.selected === true\"\r\n [attr.role]=\"item.selected === undefined ? 'menuitem' : 'menuitemradio'\"\r\n [attr.aria-checked]=\"item.selected === undefined ? null : item.selected\"\r\n [attr.aria-haspopup]=\"item.children?.length ? 'menu' : null\"\r\n [attr.aria-expanded]=\"item.children?.length ? (openSubmenuId() === item.id) : null\"\r\n (click)=\"onItemActivate(item, $event)\"\r\n (mouseenter)=\"onItemMouseEnter(item, $event)\"\r\n (mouseleave)=\"onItemMouseLeave()\">\r\n <i [class]=\"'pi ' + item.icon\" aria-hidden=\"true\"></i>\r\n <span>{{ item.label }}</span>\r\n @if (item.children?.length) {\r\n <i\r\n class=\"pi pi-angle-right menu-item__submenu-caret\"\r\n [class.is-rtl]=\"anchorRtl()\"\r\n aria-hidden=\"true\"></i>\r\n } @else if (item.selected !== undefined) {\r\n <i\r\n class=\"pi pi-check menu-item__check\"\r\n [class.menu-item__check--visible]=\"item.selected\"\r\n aria-hidden=\"true\"></i>\r\n }\r\n </button>\r\n }\r\n }\r\n\r\n @if (openSubmenuSections().length > 0) {\r\n <fly-context-menu\r\n #childMenu\r\n [anchor]=\"openSubmenuAnchorEl()\"\r\n placement=\"side\"\r\n [sections]=\"openSubmenuSections()\"\r\n (action)=\"onSubmenuAction($event)\"\r\n (closed)=\"onSubmenuClosed()\" />\r\n }\r\n</div>\r\n", styles: [":host{display:contents}.context-menu{position:fixed;z-index:1000;inline-size:max-content;min-inline-size:14ch;max-inline-size:min(38ch,100vw - 32px);padding:6px;border-radius:14px;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-a),var(--mat-menu-b));box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset);-webkit-backdrop-filter:var(--glass2-blur);backdrop-filter:var(--glass2-blur)}@media(prefers-reduced-transparency:reduce){.context-menu{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.context-menu:before,.context-menu:after{display:none}}@media(prefers-contrast:more){.context-menu{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.context-menu:after{animation:none}}.context-menu{animation:menuIn .24s var(--nova-ease-structural) both;transform-origin:top left}.menu-section-label{padding:6px 12px 4px;font:var(--nova-font-eyebrow);letter-spacing:var(--nova-tracking-eyebrow);text-transform:uppercase;color:var(--w42);pointer-events:none}.menu-item{display:flex;align-items:center;gap:10px;width:100%;min-height:32px;padding:7px 9px;border-radius:8px;color:var(--w95);font:var(--nova-font-menu-item);letter-spacing:var(--nova-tracking-menu-item);justify-content:flex-start;text-align:start}.menu-item span{min-inline-size:0;overflow-wrap:break-word}.menu-item i{flex:0 0 18px;font-size:14px;width:18px;text-align:center;opacity:.7}.menu-item__submenu-caret{flex:0 0 14px;width:14px;font-size:11px;margin-inline-start:auto;opacity:.55}.menu-item__submenu-caret.is-rtl{transform:scaleX(-1)}.menu-item__check{flex:0 0 14px;width:14px;font-size:13px;margin-inline-start:auto;color:var(--accent);opacity:0}.menu-item__check--visible{opacity:1}.menu-item--selected{background:var(--w08);font-weight:600}.menu-item:hover,.menu-item:focus-visible{background:var(--w1)}.menu-item:hover i,.menu-item:focus-visible i{opacity:1}.menu-item:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-2px}.menu-divider{height:1px;margin:4px 8px;background:var(--glass2-border)}\n"], dependencies: [{ kind: "component", type: ContextMenuComponent, selector: "fly-context-menu", inputs: ["x", "y", "anchor", "align", "placement", "sections", "boundary"], outputs: ["action", "closed"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8041
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: ContextMenuComponent, isStandalone: true, selector: "fly-context-menu", inputs: { x: { classPropertyName: "x", publicName: "x", isSignal: true, isRequired: false, transformFunction: null }, y: { classPropertyName: "y", publicName: "y", isSignal: true, isRequired: false, transformFunction: null }, anchor: { classPropertyName: "anchor", publicName: "anchor", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: true, transformFunction: null }, boundary: { classPropertyName: "boundary", publicName: "boundary", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { action: "action", closed: "closed" }, host: { listeners: { "document:mousedown": "onClickOutside($event)", "document:keydown.escape": "onEscape()", "document:contextmenu": "onContextMenu($event)", "document:keydown": "onKeydown($event)" } }, viewQueries: [{ propertyName: "menuEl", first: true, predicate: ["contextMenu"], descendants: true }, { propertyName: "childMenuRef", first: true, predicate: ["childMenu"], descendants: true }], ngImport: i0, template: "<div\r\n #contextMenu\r\n class=\"context-menu\"\r\n [style.left.px]=\"clampedPos().left\"\r\n [style.top.px]=\"clampedPos().top\"\r\n role=\"menu\"\r\n [attr.aria-label]=\"'shell.context_menu' | translate\">\r\n\r\n @for (section of sections(); track $index) {\r\n @if ($index > 0) {\r\n <div class=\"menu-divider\"></div>\r\n }\r\n @if (section.label) {\r\n <div class=\"menu-section-label\">{{ section.label }}</div>\r\n }\r\n @for (item of section.items; track item.id) {\r\n <button\r\n type=\"button\"\r\n class=\"fos-btn sm rect menu-item\"\r\n [class.menu-item--has-children]=\"!!item.children?.length\"\r\n [class.menu-item--selected]=\"item.selected === true\"\r\n [attr.role]=\"item.selected === undefined ? 'menuitem' : 'menuitemradio'\"\r\n [attr.aria-checked]=\"item.selected === undefined ? null : item.selected\"\r\n [attr.aria-haspopup]=\"item.children?.length ? 'menu' : null\"\r\n [attr.aria-expanded]=\"item.children?.length ? (openSubmenuId() === item.id) : null\"\r\n (click)=\"onItemActivate(item, $event)\"\r\n (mouseenter)=\"onItemMouseEnter(item, $event)\"\r\n (mouseleave)=\"onItemMouseLeave()\">\r\n <i [class]=\"'pi ' + item.icon\" aria-hidden=\"true\"></i>\r\n <span>{{ item.label }}</span>\r\n @if (item.children?.length) {\r\n <i\r\n class=\"pi pi-angle-right menu-item__submenu-caret\"\r\n [class.is-rtl]=\"anchorRtl()\"\r\n aria-hidden=\"true\"></i>\r\n } @else if (item.selected !== undefined) {\r\n <i\r\n class=\"pi pi-check menu-item__check\"\r\n [class.menu-item__check--visible]=\"item.selected\"\r\n aria-hidden=\"true\"></i>\r\n }\r\n </button>\r\n }\r\n }\r\n\r\n @if (openSubmenuSections().length > 0) {\r\n <fly-context-menu\r\n #childMenu\r\n [anchor]=\"openSubmenuAnchorEl()\"\r\n placement=\"side\"\r\n [sections]=\"openSubmenuSections()\"\r\n (action)=\"onSubmenuAction($event)\"\r\n (closed)=\"onSubmenuClosed()\" />\r\n }\r\n</div>\r\n", styles: [":host{display:contents}.context-menu{position:fixed;z-index:1000;inline-size:max-content;min-inline-size:14ch;max-inline-size:min(38ch,100vw - 32px);padding:6px;border-radius:14px;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-a),var(--mat-menu-b));box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset);-webkit-backdrop-filter:var(--glass2-blur);backdrop-filter:var(--glass2-blur)}@media(prefers-reduced-transparency:reduce){.context-menu{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.context-menu:before,.context-menu:after{display:none}}@media(prefers-contrast:more){.context-menu{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.context-menu:after{animation:none}}.context-menu{animation:menuIn .24s var(--nova-ease-structural) both;transform-origin:top left}.menu-section-label{padding:6px 12px 4px;font:var(--nova-font-eyebrow);letter-spacing:var(--nova-tracking-eyebrow);text-transform:uppercase;color:var(--w5);pointer-events:none}.menu-item{display:flex;align-items:center;gap:10px;width:100%;min-height:32px;padding:7px 9px;border-radius:8px;color:var(--w95);font:var(--nova-font-menu-item);letter-spacing:var(--nova-tracking-menu-item);justify-content:flex-start;text-align:start}.menu-item span{min-inline-size:0;overflow-wrap:break-word}.menu-item i{flex:0 0 18px;font-size:14px;width:18px;text-align:center;opacity:.7}.menu-item__submenu-caret{flex:0 0 14px;width:14px;font-size:11px;margin-inline-start:auto;opacity:.55}.menu-item__submenu-caret.is-rtl{transform:scaleX(-1)}.menu-item__check{flex:0 0 14px;width:14px;font-size:13px;margin-inline-start:auto;color:var(--accent);opacity:0}.menu-item__check--visible{opacity:1}.menu-item--selected{background:var(--w08);font-weight:600}.menu-item:hover,.menu-item:focus-visible{background:var(--w1)}.menu-item:hover i,.menu-item:focus-visible i{opacity:1}.menu-item:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-2px}.menu-divider{height:1px;margin:4px 8px;background:var(--glass2-border)}@media(pointer:coarse){.menu-item{min-block-size:44px}}\n"], dependencies: [{ kind: "component", type: ContextMenuComponent, selector: "fly-context-menu", inputs: ["x", "y", "anchor", "align", "placement", "sections", "boundary"], outputs: ["action", "closed"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7983
8042
  }
7984
8043
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: ContextMenuComponent, decorators: [{
7985
8044
  type: Component,
7986
- args: [{ selector: 'fly-context-menu', standalone: true, imports: [TranslatePipe, ContextMenuComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\r\n #contextMenu\r\n class=\"context-menu\"\r\n [style.left.px]=\"clampedPos().left\"\r\n [style.top.px]=\"clampedPos().top\"\r\n role=\"menu\"\r\n [attr.aria-label]=\"'shell.context_menu' | translate\">\r\n\r\n @for (section of sections(); track $index) {\r\n @if ($index > 0) {\r\n <div class=\"menu-divider\"></div>\r\n }\r\n @if (section.label) {\r\n <div class=\"menu-section-label\">{{ section.label }}</div>\r\n }\r\n @for (item of section.items; track item.id) {\r\n <button\r\n type=\"button\"\r\n class=\"fos-btn sm rect menu-item\"\r\n [class.menu-item--has-children]=\"!!item.children?.length\"\r\n [class.menu-item--selected]=\"item.selected === true\"\r\n [attr.role]=\"item.selected === undefined ? 'menuitem' : 'menuitemradio'\"\r\n [attr.aria-checked]=\"item.selected === undefined ? null : item.selected\"\r\n [attr.aria-haspopup]=\"item.children?.length ? 'menu' : null\"\r\n [attr.aria-expanded]=\"item.children?.length ? (openSubmenuId() === item.id) : null\"\r\n (click)=\"onItemActivate(item, $event)\"\r\n (mouseenter)=\"onItemMouseEnter(item, $event)\"\r\n (mouseleave)=\"onItemMouseLeave()\">\r\n <i [class]=\"'pi ' + item.icon\" aria-hidden=\"true\"></i>\r\n <span>{{ item.label }}</span>\r\n @if (item.children?.length) {\r\n <i\r\n class=\"pi pi-angle-right menu-item__submenu-caret\"\r\n [class.is-rtl]=\"anchorRtl()\"\r\n aria-hidden=\"true\"></i>\r\n } @else if (item.selected !== undefined) {\r\n <i\r\n class=\"pi pi-check menu-item__check\"\r\n [class.menu-item__check--visible]=\"item.selected\"\r\n aria-hidden=\"true\"></i>\r\n }\r\n </button>\r\n }\r\n }\r\n\r\n @if (openSubmenuSections().length > 0) {\r\n <fly-context-menu\r\n #childMenu\r\n [anchor]=\"openSubmenuAnchorEl()\"\r\n placement=\"side\"\r\n [sections]=\"openSubmenuSections()\"\r\n (action)=\"onSubmenuAction($event)\"\r\n (closed)=\"onSubmenuClosed()\" />\r\n }\r\n</div>\r\n", styles: [":host{display:contents}.context-menu{position:fixed;z-index:1000;inline-size:max-content;min-inline-size:14ch;max-inline-size:min(38ch,100vw - 32px);padding:6px;border-radius:14px;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-a),var(--mat-menu-b));box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset);-webkit-backdrop-filter:var(--glass2-blur);backdrop-filter:var(--glass2-blur)}@media(prefers-reduced-transparency:reduce){.context-menu{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.context-menu:before,.context-menu:after{display:none}}@media(prefers-contrast:more){.context-menu{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.context-menu:after{animation:none}}.context-menu{animation:menuIn .24s var(--nova-ease-structural) both;transform-origin:top left}.menu-section-label{padding:6px 12px 4px;font:var(--nova-font-eyebrow);letter-spacing:var(--nova-tracking-eyebrow);text-transform:uppercase;color:var(--w42);pointer-events:none}.menu-item{display:flex;align-items:center;gap:10px;width:100%;min-height:32px;padding:7px 9px;border-radius:8px;color:var(--w95);font:var(--nova-font-menu-item);letter-spacing:var(--nova-tracking-menu-item);justify-content:flex-start;text-align:start}.menu-item span{min-inline-size:0;overflow-wrap:break-word}.menu-item i{flex:0 0 18px;font-size:14px;width:18px;text-align:center;opacity:.7}.menu-item__submenu-caret{flex:0 0 14px;width:14px;font-size:11px;margin-inline-start:auto;opacity:.55}.menu-item__submenu-caret.is-rtl{transform:scaleX(-1)}.menu-item__check{flex:0 0 14px;width:14px;font-size:13px;margin-inline-start:auto;color:var(--accent);opacity:0}.menu-item__check--visible{opacity:1}.menu-item--selected{background:var(--w08);font-weight:600}.menu-item:hover,.menu-item:focus-visible{background:var(--w1)}.menu-item:hover i,.menu-item:focus-visible i{opacity:1}.menu-item:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-2px}.menu-divider{height:1px;margin:4px 8px;background:var(--glass2-border)}\n"] }]
8045
+ args: [{ selector: 'fly-context-menu', standalone: true, imports: [TranslatePipe, ContextMenuComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\r\n #contextMenu\r\n class=\"context-menu\"\r\n [style.left.px]=\"clampedPos().left\"\r\n [style.top.px]=\"clampedPos().top\"\r\n role=\"menu\"\r\n [attr.aria-label]=\"'shell.context_menu' | translate\">\r\n\r\n @for (section of sections(); track $index) {\r\n @if ($index > 0) {\r\n <div class=\"menu-divider\"></div>\r\n }\r\n @if (section.label) {\r\n <div class=\"menu-section-label\">{{ section.label }}</div>\r\n }\r\n @for (item of section.items; track item.id) {\r\n <button\r\n type=\"button\"\r\n class=\"fos-btn sm rect menu-item\"\r\n [class.menu-item--has-children]=\"!!item.children?.length\"\r\n [class.menu-item--selected]=\"item.selected === true\"\r\n [attr.role]=\"item.selected === undefined ? 'menuitem' : 'menuitemradio'\"\r\n [attr.aria-checked]=\"item.selected === undefined ? null : item.selected\"\r\n [attr.aria-haspopup]=\"item.children?.length ? 'menu' : null\"\r\n [attr.aria-expanded]=\"item.children?.length ? (openSubmenuId() === item.id) : null\"\r\n (click)=\"onItemActivate(item, $event)\"\r\n (mouseenter)=\"onItemMouseEnter(item, $event)\"\r\n (mouseleave)=\"onItemMouseLeave()\">\r\n <i [class]=\"'pi ' + item.icon\" aria-hidden=\"true\"></i>\r\n <span>{{ item.label }}</span>\r\n @if (item.children?.length) {\r\n <i\r\n class=\"pi pi-angle-right menu-item__submenu-caret\"\r\n [class.is-rtl]=\"anchorRtl()\"\r\n aria-hidden=\"true\"></i>\r\n } @else if (item.selected !== undefined) {\r\n <i\r\n class=\"pi pi-check menu-item__check\"\r\n [class.menu-item__check--visible]=\"item.selected\"\r\n aria-hidden=\"true\"></i>\r\n }\r\n </button>\r\n }\r\n }\r\n\r\n @if (openSubmenuSections().length > 0) {\r\n <fly-context-menu\r\n #childMenu\r\n [anchor]=\"openSubmenuAnchorEl()\"\r\n placement=\"side\"\r\n [sections]=\"openSubmenuSections()\"\r\n (action)=\"onSubmenuAction($event)\"\r\n (closed)=\"onSubmenuClosed()\" />\r\n }\r\n</div>\r\n", styles: [":host{display:contents}.context-menu{position:fixed;z-index:1000;inline-size:max-content;min-inline-size:14ch;max-inline-size:min(38ch,100vw - 32px);padding:6px;border-radius:14px;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-a),var(--mat-menu-b));box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset);-webkit-backdrop-filter:var(--glass2-blur);backdrop-filter:var(--glass2-blur)}@media(prefers-reduced-transparency:reduce){.context-menu{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.context-menu:before,.context-menu:after{display:none}}@media(prefers-contrast:more){.context-menu{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.context-menu:after{animation:none}}.context-menu{animation:menuIn .24s var(--nova-ease-structural) both;transform-origin:top left}.menu-section-label{padding:6px 12px 4px;font:var(--nova-font-eyebrow);letter-spacing:var(--nova-tracking-eyebrow);text-transform:uppercase;color:var(--w5);pointer-events:none}.menu-item{display:flex;align-items:center;gap:10px;width:100%;min-height:32px;padding:7px 9px;border-radius:8px;color:var(--w95);font:var(--nova-font-menu-item);letter-spacing:var(--nova-tracking-menu-item);justify-content:flex-start;text-align:start}.menu-item span{min-inline-size:0;overflow-wrap:break-word}.menu-item i{flex:0 0 18px;font-size:14px;width:18px;text-align:center;opacity:.7}.menu-item__submenu-caret{flex:0 0 14px;width:14px;font-size:11px;margin-inline-start:auto;opacity:.55}.menu-item__submenu-caret.is-rtl{transform:scaleX(-1)}.menu-item__check{flex:0 0 14px;width:14px;font-size:13px;margin-inline-start:auto;color:var(--accent);opacity:0}.menu-item__check--visible{opacity:1}.menu-item--selected{background:var(--w08);font-weight:600}.menu-item:hover,.menu-item:focus-visible{background:var(--w1)}.menu-item:hover i,.menu-item:focus-visible i{opacity:1}.menu-item:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-2px}.menu-divider{height:1px;margin:4px 8px;background:var(--glass2-border)}@media(pointer:coarse){.menu-item{min-block-size:44px}}\n"] }]
7987
8046
  }], propDecorators: { menuEl: [{
7988
8047
  type: ViewChild,
7989
8048
  args: ['contextMenu']
@@ -8160,15 +8219,70 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
8160
8219
  args: [{ providedIn: 'root' }]
8161
8220
  }] });
8162
8221
 
8222
+ /**
8223
+ * Whether the host application is currently wearing MOBILE chrome.
8224
+ *
8225
+ * ## Why the DS asks instead of measuring
8226
+ * A second `matchMedia` inside the design system would be a second breakpoint
8227
+ * mechanism, and two predicates that answer "is this a phone" drift the first time
8228
+ * anyone retunes one of them. The shell already owns exactly one such predicate
8229
+ * (`ShellViewportService`, S5.1) and mirrors it three ways — a class on `<html>`, a
8230
+ * signal for templates, and the shell's own inset arithmetic. This token is the
8231
+ * fourth route to that SAME answer, not a new one: the shell binds it to
8232
+ * `ShellViewportService.isMobile` and nothing here evaluates a query.
8233
+ *
8234
+ * ## Default: `false`, deliberately
8235
+ * Unprovided, every DS overlay behaves exactly as it did before this token existed
8236
+ * — desktop geometry, no mobile branch. That matters for two populations:
8237
+ *
8238
+ * - **Standalone External Apps** (Circles/Thoughts/PPM) render on their own page
8239
+ * with no FlyOS shell. They opt in by providing this from their own breakpoint
8240
+ * source; until they do, nothing about them changes.
8241
+ * - **Tests and Storybook-style harnesses** that construct a DS component bare.
8242
+ *
8243
+ * A default of "measure the window" would have been the opposite trade: silently
8244
+ * correct in the shell, silently surprising everywhere else, and impossible to
8245
+ * override downward.
8246
+ *
8247
+ * ## Consumers
8248
+ * `fly-drawer` (`variant="auto"` → `sheet` on mobile) and `fly-message-box` /
8249
+ * `fly-confirm-dialog` (the full-bleed confirm card). Each reads the signal and
8250
+ * either resolves an input or stamps a host class — none of them re-derive a
8251
+ * width, so retuning the threshold is still a one-line change in the shell.
8252
+ *
8253
+ * @example Shell wiring (`app.config.ts`)
8254
+ * ```ts
8255
+ * {
8256
+ * provide: FLY_VIEWPORT_IS_MOBILE,
8257
+ * useFactory: () => inject(ShellViewportService).isMobile,
8258
+ * }
8259
+ * ```
8260
+ */
8261
+ const FLY_VIEWPORT_IS_MOBILE = new InjectionToken('FLY_VIEWPORT_IS_MOBILE', { providedIn: 'root', factory: () => signal(false).asReadonly() });
8262
+
8163
8263
  /**
8164
8264
  * Host component for the app-wide alert/confirm dialog surface. It renders whatever the
8165
8265
  * current `MessageBoxService` request describes (icon, message, button set) and resolves that
8166
8266
  * request with the user's choice — the design-system replacement for native `alert()`/`confirm()`
8167
8267
  * dialogs, which the platform disallows (see `feedback_no_native_browser_dialogs`).
8268
+ *
8269
+ * ## The mobile confirm card (S5.4)
8270
+ * On mobile chrome the dialog stops being a fixed 400px plate floating in the middle of
8271
+ * a desktop and becomes the design's **confirm card**: full width less a 16px margin on
8272
+ * each side, one step rounder, and with its actions on a full-width row at the 44px
8273
+ * touch floor. That is a host class, not a media query, so the threshold stays where
8274
+ * S5.1 put it — see {@link FLY_VIEWPORT_IS_MOBILE}.
8275
+ *
8276
+ * This is the confirm surface users actually meet: `MessageBoxService` has ~59 call
8277
+ * sites across the shell and the core apps, while the DS's other confirm surface
8278
+ * (`fly-confirm-dialog`, the standalone business-app kit's port) has none outside the
8279
+ * design lab. Both got the treatment; only this one changes what anyone sees today.
8168
8280
  */
8169
8281
  class MessageBoxComponent {
8170
8282
  injectedService = inject(MessageBoxService);
8171
8283
  elRef = inject(ElementRef);
8284
+ /** Mobile chrome → the full-bleed confirm card. Read by the host class binding. */
8285
+ isMobile = inject(FLY_VIEWPORT_IS_MOBILE);
8172
8286
  /**
8173
8287
  * Optional service override. When bound, the component renders THIS instance's
8174
8288
  * state instead of the root singleton — the mechanism that lets each desktop
@@ -8249,11 +8363,11 @@ class MessageBoxComponent {
8249
8363
  this.previouslyFocused = null;
8250
8364
  }
8251
8365
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: MessageBoxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8252
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: MessageBoxComponent, isStandalone: true, selector: "fly-message-box", inputs: { service: { classPropertyName: "service", publicName: "service", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: "@if (activeService.visible()) {\r\n <div class=\"mb-backdrop\">\r\n <div\r\n class=\"mb-scrim\"\r\n role=\"button\"\r\n tabindex=\"0\"\r\n [attr.aria-label]=\"'common.action.cancel' | translate\"\r\n (click)=\"onBackdropClick()\"\r\n (keydown.enter)=\"onBackdropClick()\"\r\n (keydown.space)=\"onBackdropSpace($event)\"></div>\r\n <div\r\n class=\"mb-dialog\"\r\n [class.mb-info]=\"activeService.icon() === MessageBoxIcon.Information\"\r\n [class.mb-warning]=\"activeService.icon() === MessageBoxIcon.Warning\"\r\n [class.mb-danger]=\"activeService.icon() === MessageBoxIcon.Error\"\r\n [class.mb-question]=\"activeService.icon() === MessageBoxIcon.Question\"\r\n role=\"alertdialog\"\r\n aria-modal=\"true\"\r\n aria-labelledby=\"mb-title\"\r\n [attr.aria-describedby]=\"ariaDescribedBy()\">\r\n\r\n @if (iconClass()) {\r\n <div class=\"mb-icon-wrap\">\r\n <i [class]=\"iconClass() + ' mb-icon'\" aria-hidden=\"true\"></i>\r\n </div>\r\n }\r\n\r\n <div class=\"mb-title\" id=\"mb-title\">{{ activeService.title() }}</div>\r\n <div class=\"mb-message\" id=\"mb-message\">{{ activeService.message() }}</div>\r\n\r\n @if (dontAskAgainConfig(); as daa) {\r\n <label class=\"mb-dont-ask-again\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"dontAskAgainChecked()\"\r\n (change)=\"onDontAskAgainToggle($event)\" />\r\n <span id=\"mb-dont-ask\">{{ daa.labelKey | translate }}</span>\r\n </label>\r\n }\r\n\r\n <div class=\"mb-actions\">\r\n @for (btn of activeService.buttons(); track btn.result) {\r\n <button\r\n type=\"button\"\r\n class=\"fos-btn sm mb-btn\"\r\n [class.mb-btn--primary]=\"btn.variant === 'primary'\"\r\n [class.mb-btn--danger]=\"btn.variant === 'danger'\"\r\n (click)=\"onButtonClick(btn.result)\">\r\n {{ btn.label }}\r\n </button>\r\n }\r\n </div>\r\n\r\n </div>\r\n </div>\r\n}\r\n", styles: [".mb-backdrop{position:absolute;inset:0;z-index:5100;display:flex;align-items:center;justify-content:center}.mb-scrim{position:absolute;inset:0;background:#00000073;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);animation:mbFadeIn .15s ease both;border:none;padding:0;margin:0;cursor:pointer}@keyframes mbFadeIn{0%{opacity:0}to{opacity:1}}.mb-dialog{position:relative;z-index:1;background:var(--surface-card, rgba(30, 30, 30, .98));border:1px solid var(--surface-border);border-radius:16px;padding:28px 28px 20px;width:400px;max-width:90%;display:flex;flex-direction:column;align-items:center;text-align:center;gap:6px;box-shadow:0 20px 60px #0006;animation:mbScaleIn .2s cubic-bezier(.22,1,.36,1) both}@keyframes mbScaleIn{0%{opacity:0;transform:scale(.92)}to{opacity:1;transform:scale(1)}}.mb-icon-wrap{width:52px;height:52px;border-radius:50%;display:flex;align-items:center;justify-content:center;margin-bottom:6px}.mb-icon{font-size:24px}.mb-info .mb-icon-wrap{background:#3b82f61f}.mb-info .mb-icon{color:#3b82f6}.mb-warning .mb-icon-wrap{background:#f59e0b1f}.mb-warning .mb-icon{color:#f59e0b}.mb-danger .mb-icon-wrap{background:#ef44441f}.mb-danger .mb-icon{color:#ef4444}.mb-question .mb-icon-wrap{background:#8b5cf61f}.mb-question .mb-icon{color:#8b5cf6}.mb-title{font-size:15px;font-weight:700;color:var(--text-color)}.mb-message{font-size:13px;color:var(--text-color-secondary);line-height:1.55;max-width:340px;white-space:pre-line}.mb-dont-ask-again{display:flex;align-items:center;gap:8px;margin-block-start:12px;font-size:12px;color:var(--text-color-secondary);cursor:pointer;align-self:flex-start;-webkit-user-select:none;user-select:none}.mb-dont-ask-again input[type=checkbox]{margin:0;cursor:pointer}.mb-dont-ask-again span{line-height:1.3}.mb-actions{display:flex;gap:10px;margin-top:16px;width:100%;justify-content:center;flex-wrap:wrap}.mb-btn{min-width:90px}.mb-btn--primary{background:var(--primary-color);color:#fff}.mb-btn--primary:hover{filter:brightness(1.1)}.mb-btn--danger{background:#ef4444;color:#fff}.mb-btn--danger:hover{background:#dc2626}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8366
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: MessageBoxComponent, isStandalone: true, selector: "fly-message-box", inputs: { service: { classPropertyName: "service", publicName: "service", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "document:keydown.escape": "onEscape()" }, properties: { "class.fly-message-box--mobile": "isMobile()" } }, ngImport: i0, template: "@if (activeService.visible()) {\r\n <div class=\"mb-backdrop\">\r\n <div\r\n class=\"mb-scrim\"\r\n role=\"button\"\r\n tabindex=\"0\"\r\n [attr.aria-label]=\"'common.action.cancel' | translate\"\r\n (click)=\"onBackdropClick()\"\r\n (keydown.enter)=\"onBackdropClick()\"\r\n (keydown.space)=\"onBackdropSpace($event)\"></div>\r\n <div\r\n class=\"mb-dialog\"\r\n [class.mb-info]=\"activeService.icon() === MessageBoxIcon.Information\"\r\n [class.mb-warning]=\"activeService.icon() === MessageBoxIcon.Warning\"\r\n [class.mb-danger]=\"activeService.icon() === MessageBoxIcon.Error\"\r\n [class.mb-question]=\"activeService.icon() === MessageBoxIcon.Question\"\r\n role=\"alertdialog\"\r\n aria-modal=\"true\"\r\n aria-labelledby=\"mb-title\"\r\n [attr.aria-describedby]=\"ariaDescribedBy()\">\r\n\r\n @if (iconClass()) {\r\n <div class=\"mb-icon-wrap\">\r\n <i [class]=\"iconClass() + ' mb-icon'\" aria-hidden=\"true\"></i>\r\n </div>\r\n }\r\n\r\n <div class=\"mb-title\" id=\"mb-title\">{{ activeService.title() }}</div>\r\n <div class=\"mb-message\" id=\"mb-message\">{{ activeService.message() }}</div>\r\n\r\n @if (dontAskAgainConfig(); as daa) {\r\n <label class=\"mb-dont-ask-again\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"dontAskAgainChecked()\"\r\n (change)=\"onDontAskAgainToggle($event)\" />\r\n <span id=\"mb-dont-ask\">{{ daa.labelKey | translate }}</span>\r\n </label>\r\n }\r\n\r\n <div class=\"mb-actions\">\r\n @for (btn of activeService.buttons(); track btn.result) {\r\n <button\r\n type=\"button\"\r\n class=\"fos-btn sm mb-btn\"\r\n [class.mb-btn--primary]=\"btn.variant === 'primary'\"\r\n [class.mb-btn--danger]=\"btn.variant === 'danger'\"\r\n (click)=\"onButtonClick(btn.result)\">\r\n {{ btn.label }}\r\n </button>\r\n }\r\n </div>\r\n\r\n </div>\r\n </div>\r\n}\r\n", styles: [".mb-backdrop{position:absolute;inset:0;z-index:5100;display:flex;align-items:center;justify-content:center}.mb-scrim{position:absolute;inset:0;background:#00000073;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);animation:mbFadeIn .15s ease both;border:none;padding:0;margin:0;cursor:pointer}@keyframes mbFadeIn{0%{opacity:0}to{opacity:1}}.mb-dialog{position:relative;z-index:1;background:var(--surface-card, rgba(30, 30, 30, .98));border:1px solid var(--surface-border);border-radius:16px;padding:28px 28px 20px;width:400px;max-width:90%;display:flex;flex-direction:column;align-items:center;text-align:center;gap:6px;box-shadow:0 20px 60px #0006;animation:mbScaleIn .2s cubic-bezier(.22,1,.36,1) both}@keyframes mbScaleIn{0%{opacity:0;transform:scale(.92)}to{opacity:1;transform:scale(1)}}.mb-icon-wrap{width:52px;height:52px;border-radius:50%;display:flex;align-items:center;justify-content:center;margin-bottom:6px}.mb-icon{font-size:24px}.mb-info .mb-icon-wrap{background:#3b82f61f}.mb-info .mb-icon{color:#3b82f6}.mb-warning .mb-icon-wrap{background:#f59e0b1f}.mb-warning .mb-icon{color:#f59e0b}.mb-danger .mb-icon-wrap{background:#ef44441f}.mb-danger .mb-icon{color:#ef4444}.mb-question .mb-icon-wrap{background:#8b5cf61f}.mb-question .mb-icon{color:#8b5cf6}.mb-title{font-size:15px;font-weight:700;color:var(--text-color)}.mb-message{font-size:13px;color:var(--text-color-secondary);line-height:1.55;max-width:340px;white-space:pre-line}.mb-dont-ask-again{display:flex;align-items:center;gap:8px;margin-block-start:12px;font-size:12px;color:var(--text-color-secondary);cursor:pointer;align-self:flex-start;-webkit-user-select:none;user-select:none}.mb-dont-ask-again input[type=checkbox]{margin:0;cursor:pointer}.mb-dont-ask-again span{line-height:1.3}.mb-actions{display:flex;gap:10px;margin-top:16px;width:100%;justify-content:center;flex-wrap:wrap}.mb-btn{min-width:90px}.mb-btn--primary{background:var(--primary-color);color:#fff}.mb-btn--primary:hover{filter:brightness(1.1)}.mb-btn--danger{background:#ef4444;color:#fff}.mb-btn--danger:hover{background:#dc2626}:host(.fly-message-box--mobile) .mb-backdrop{padding:16px}:host(.fly-message-box--mobile) .mb-dialog{inline-size:100%;max-inline-size:none;border-radius:18px;padding:24px 20px 16px}:host(.fly-message-box--mobile) .mb-actions{gap:8px;flex-wrap:nowrap}:host(.fly-message-box--mobile) .mb-btn{block-size:44px;flex:1 1 0;min-inline-size:0}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8253
8367
  }
8254
8368
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: MessageBoxComponent, decorators: [{
8255
8369
  type: Component,
8256
- args: [{ selector: 'fly-message-box', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (activeService.visible()) {\r\n <div class=\"mb-backdrop\">\r\n <div\r\n class=\"mb-scrim\"\r\n role=\"button\"\r\n tabindex=\"0\"\r\n [attr.aria-label]=\"'common.action.cancel' | translate\"\r\n (click)=\"onBackdropClick()\"\r\n (keydown.enter)=\"onBackdropClick()\"\r\n (keydown.space)=\"onBackdropSpace($event)\"></div>\r\n <div\r\n class=\"mb-dialog\"\r\n [class.mb-info]=\"activeService.icon() === MessageBoxIcon.Information\"\r\n [class.mb-warning]=\"activeService.icon() === MessageBoxIcon.Warning\"\r\n [class.mb-danger]=\"activeService.icon() === MessageBoxIcon.Error\"\r\n [class.mb-question]=\"activeService.icon() === MessageBoxIcon.Question\"\r\n role=\"alertdialog\"\r\n aria-modal=\"true\"\r\n aria-labelledby=\"mb-title\"\r\n [attr.aria-describedby]=\"ariaDescribedBy()\">\r\n\r\n @if (iconClass()) {\r\n <div class=\"mb-icon-wrap\">\r\n <i [class]=\"iconClass() + ' mb-icon'\" aria-hidden=\"true\"></i>\r\n </div>\r\n }\r\n\r\n <div class=\"mb-title\" id=\"mb-title\">{{ activeService.title() }}</div>\r\n <div class=\"mb-message\" id=\"mb-message\">{{ activeService.message() }}</div>\r\n\r\n @if (dontAskAgainConfig(); as daa) {\r\n <label class=\"mb-dont-ask-again\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"dontAskAgainChecked()\"\r\n (change)=\"onDontAskAgainToggle($event)\" />\r\n <span id=\"mb-dont-ask\">{{ daa.labelKey | translate }}</span>\r\n </label>\r\n }\r\n\r\n <div class=\"mb-actions\">\r\n @for (btn of activeService.buttons(); track btn.result) {\r\n <button\r\n type=\"button\"\r\n class=\"fos-btn sm mb-btn\"\r\n [class.mb-btn--primary]=\"btn.variant === 'primary'\"\r\n [class.mb-btn--danger]=\"btn.variant === 'danger'\"\r\n (click)=\"onButtonClick(btn.result)\">\r\n {{ btn.label }}\r\n </button>\r\n }\r\n </div>\r\n\r\n </div>\r\n </div>\r\n}\r\n", styles: [".mb-backdrop{position:absolute;inset:0;z-index:5100;display:flex;align-items:center;justify-content:center}.mb-scrim{position:absolute;inset:0;background:#00000073;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);animation:mbFadeIn .15s ease both;border:none;padding:0;margin:0;cursor:pointer}@keyframes mbFadeIn{0%{opacity:0}to{opacity:1}}.mb-dialog{position:relative;z-index:1;background:var(--surface-card, rgba(30, 30, 30, .98));border:1px solid var(--surface-border);border-radius:16px;padding:28px 28px 20px;width:400px;max-width:90%;display:flex;flex-direction:column;align-items:center;text-align:center;gap:6px;box-shadow:0 20px 60px #0006;animation:mbScaleIn .2s cubic-bezier(.22,1,.36,1) both}@keyframes mbScaleIn{0%{opacity:0;transform:scale(.92)}to{opacity:1;transform:scale(1)}}.mb-icon-wrap{width:52px;height:52px;border-radius:50%;display:flex;align-items:center;justify-content:center;margin-bottom:6px}.mb-icon{font-size:24px}.mb-info .mb-icon-wrap{background:#3b82f61f}.mb-info .mb-icon{color:#3b82f6}.mb-warning .mb-icon-wrap{background:#f59e0b1f}.mb-warning .mb-icon{color:#f59e0b}.mb-danger .mb-icon-wrap{background:#ef44441f}.mb-danger .mb-icon{color:#ef4444}.mb-question .mb-icon-wrap{background:#8b5cf61f}.mb-question .mb-icon{color:#8b5cf6}.mb-title{font-size:15px;font-weight:700;color:var(--text-color)}.mb-message{font-size:13px;color:var(--text-color-secondary);line-height:1.55;max-width:340px;white-space:pre-line}.mb-dont-ask-again{display:flex;align-items:center;gap:8px;margin-block-start:12px;font-size:12px;color:var(--text-color-secondary);cursor:pointer;align-self:flex-start;-webkit-user-select:none;user-select:none}.mb-dont-ask-again input[type=checkbox]{margin:0;cursor:pointer}.mb-dont-ask-again span{line-height:1.3}.mb-actions{display:flex;gap:10px;margin-top:16px;width:100%;justify-content:center;flex-wrap:wrap}.mb-btn{min-width:90px}.mb-btn--primary{background:var(--primary-color);color:#fff}.mb-btn--primary:hover{filter:brightness(1.1)}.mb-btn--danger{background:#ef4444;color:#fff}.mb-btn--danger:hover{background:#dc2626}\n"] }]
8370
+ args: [{ selector: 'fly-message-box', standalone: true, imports: [TranslatePipe], host: { '[class.fly-message-box--mobile]': 'isMobile()' }, changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (activeService.visible()) {\r\n <div class=\"mb-backdrop\">\r\n <div\r\n class=\"mb-scrim\"\r\n role=\"button\"\r\n tabindex=\"0\"\r\n [attr.aria-label]=\"'common.action.cancel' | translate\"\r\n (click)=\"onBackdropClick()\"\r\n (keydown.enter)=\"onBackdropClick()\"\r\n (keydown.space)=\"onBackdropSpace($event)\"></div>\r\n <div\r\n class=\"mb-dialog\"\r\n [class.mb-info]=\"activeService.icon() === MessageBoxIcon.Information\"\r\n [class.mb-warning]=\"activeService.icon() === MessageBoxIcon.Warning\"\r\n [class.mb-danger]=\"activeService.icon() === MessageBoxIcon.Error\"\r\n [class.mb-question]=\"activeService.icon() === MessageBoxIcon.Question\"\r\n role=\"alertdialog\"\r\n aria-modal=\"true\"\r\n aria-labelledby=\"mb-title\"\r\n [attr.aria-describedby]=\"ariaDescribedBy()\">\r\n\r\n @if (iconClass()) {\r\n <div class=\"mb-icon-wrap\">\r\n <i [class]=\"iconClass() + ' mb-icon'\" aria-hidden=\"true\"></i>\r\n </div>\r\n }\r\n\r\n <div class=\"mb-title\" id=\"mb-title\">{{ activeService.title() }}</div>\r\n <div class=\"mb-message\" id=\"mb-message\">{{ activeService.message() }}</div>\r\n\r\n @if (dontAskAgainConfig(); as daa) {\r\n <label class=\"mb-dont-ask-again\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"dontAskAgainChecked()\"\r\n (change)=\"onDontAskAgainToggle($event)\" />\r\n <span id=\"mb-dont-ask\">{{ daa.labelKey | translate }}</span>\r\n </label>\r\n }\r\n\r\n <div class=\"mb-actions\">\r\n @for (btn of activeService.buttons(); track btn.result) {\r\n <button\r\n type=\"button\"\r\n class=\"fos-btn sm mb-btn\"\r\n [class.mb-btn--primary]=\"btn.variant === 'primary'\"\r\n [class.mb-btn--danger]=\"btn.variant === 'danger'\"\r\n (click)=\"onButtonClick(btn.result)\">\r\n {{ btn.label }}\r\n </button>\r\n }\r\n </div>\r\n\r\n </div>\r\n </div>\r\n}\r\n", styles: [".mb-backdrop{position:absolute;inset:0;z-index:5100;display:flex;align-items:center;justify-content:center}.mb-scrim{position:absolute;inset:0;background:#00000073;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);animation:mbFadeIn .15s ease both;border:none;padding:0;margin:0;cursor:pointer}@keyframes mbFadeIn{0%{opacity:0}to{opacity:1}}.mb-dialog{position:relative;z-index:1;background:var(--surface-card, rgba(30, 30, 30, .98));border:1px solid var(--surface-border);border-radius:16px;padding:28px 28px 20px;width:400px;max-width:90%;display:flex;flex-direction:column;align-items:center;text-align:center;gap:6px;box-shadow:0 20px 60px #0006;animation:mbScaleIn .2s cubic-bezier(.22,1,.36,1) both}@keyframes mbScaleIn{0%{opacity:0;transform:scale(.92)}to{opacity:1;transform:scale(1)}}.mb-icon-wrap{width:52px;height:52px;border-radius:50%;display:flex;align-items:center;justify-content:center;margin-bottom:6px}.mb-icon{font-size:24px}.mb-info .mb-icon-wrap{background:#3b82f61f}.mb-info .mb-icon{color:#3b82f6}.mb-warning .mb-icon-wrap{background:#f59e0b1f}.mb-warning .mb-icon{color:#f59e0b}.mb-danger .mb-icon-wrap{background:#ef44441f}.mb-danger .mb-icon{color:#ef4444}.mb-question .mb-icon-wrap{background:#8b5cf61f}.mb-question .mb-icon{color:#8b5cf6}.mb-title{font-size:15px;font-weight:700;color:var(--text-color)}.mb-message{font-size:13px;color:var(--text-color-secondary);line-height:1.55;max-width:340px;white-space:pre-line}.mb-dont-ask-again{display:flex;align-items:center;gap:8px;margin-block-start:12px;font-size:12px;color:var(--text-color-secondary);cursor:pointer;align-self:flex-start;-webkit-user-select:none;user-select:none}.mb-dont-ask-again input[type=checkbox]{margin:0;cursor:pointer}.mb-dont-ask-again span{line-height:1.3}.mb-actions{display:flex;gap:10px;margin-top:16px;width:100%;justify-content:center;flex-wrap:wrap}.mb-btn{min-width:90px}.mb-btn--primary{background:var(--primary-color);color:#fff}.mb-btn--primary:hover{filter:brightness(1.1)}.mb-btn--danger{background:#ef4444;color:#fff}.mb-btn--danger:hover{background:#dc2626}:host(.fly-message-box--mobile) .mb-backdrop{padding:16px}:host(.fly-message-box--mobile) .mb-dialog{inline-size:100%;max-inline-size:none;border-radius:18px;padding:24px 20px 16px}:host(.fly-message-box--mobile) .mb-actions{gap:8px;flex-wrap:nowrap}:host(.fly-message-box--mobile) .mb-btn{block-size:44px;flex:1 1 0;min-inline-size:0}\n"] }]
8257
8371
  }], propDecorators: { service: [{ type: i0.Input, args: [{ isSignal: true, alias: "service", required: false }] }], onEscape: [{
8258
8372
  type: HostListener,
8259
8373
  args: ['document:keydown.escape']
@@ -9967,16 +10081,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
9967
10081
  class FlyDrawerComponent {
9968
10082
  host = inject((ElementRef));
9969
10083
  destroyRef = inject(DestroyRef);
10084
+ /** The host app's mobile-chrome flag — the only thing `variant="auto"` consults. */
10085
+ hostIsMobile = inject(FLY_VIEWPORT_IS_MOBILE);
9970
10086
  /** Drives mount + slide. Controlled by the parent. */
9971
10087
  open = input(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
9972
10088
  /** Width tier → inline-size (sm 360 / md 480 / lg 640 / xl min(960px, 94%)). */
9973
10089
  size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
9974
10090
  /** Convenience title shown in the default header (ignored if `[flyDrawerHeader]` is projected). Treated as an i18n key. */
9975
10091
  heading = input(null, ...(ngDevMode ? [{ debugName: "heading" }] : /* istanbul ignore next */ []));
9976
- /** Edge to pin to. `end` (default) slides from inline-end; RTL flips it. Ignored when `variant` is `sheet`. */
10092
+ /** Edge to pin to. `end` (default) slides from inline-end; RTL flips it. Ignored when the resolved variant is `sheet`. */
9977
10093
  side = input('end', ...(ngDevMode ? [{ debugName: "side" }] : /* istanbul ignore next */ []));
9978
- /** `side` (default, edge-pinned) or `sheet` (bottom-anchored, full-width). See {@link FlyDrawerVariant}. */
9979
- variant = input('side', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
10094
+ /** `auto` (default — sheet on mobile), `side` (edge-pinned) or `sheet` (bottom-anchored). See {@link FlyDrawerVariant}. */
10095
+ variant = input('auto', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
9980
10096
  /** Close when the scrim is clicked. */
9981
10097
  dismissOnScrim = input(true, ...(ngDevMode ? [{ debugName: "dismissOnScrim" }] : /* istanbul ignore next */ []));
9982
10098
  /** Close on Escape. */
@@ -10006,12 +10122,23 @@ class FlyDrawerComponent {
10006
10122
  leaving = signal(false, ...(ngDevMode ? [{ debugName: "leaving" }] : /* istanbul ignore next */ []));
10007
10123
  headingId = 'fly-drawer-heading';
10008
10124
  labelledBy = computed(() => (this.heading() ? this.headingId : null), ...(ngDevMode ? [{ debugName: "labelledBy" }] : /* istanbul ignore next */ []));
10125
+ /**
10126
+ * `variant` with `'auto'` collapsed to the concrete one that renders. Everything
10127
+ * downstream (the panel class, the sheet's own geometry) reads THIS, never the raw
10128
+ * input, so `auto` is resolved in exactly one place.
10129
+ */
10130
+ resolvedVariant = computed(() => {
10131
+ const requested = this.variant();
10132
+ if (requested !== 'auto')
10133
+ return requested;
10134
+ return this.hostIsMobile() ? 'sheet' : 'side';
10135
+ }, ...(ngDevMode ? [{ debugName: "resolvedVariant" }] : /* istanbul ignore next */ []));
10009
10136
  /** Size/side classes as an ngClass map (kept off the `[class]` string binding,
10010
10137
  * which can race the leaving toggle and stutter the animation). */
10011
10138
  panelClass = computed(() => ({
10012
10139
  ['fly-drawer__panel--' + this.size()]: true,
10013
10140
  ['fly-drawer__panel--side-' + this.side()]: true,
10014
- 'fly-drawer__panel--sheet': this.variant() === 'sheet',
10141
+ 'fly-drawer__panel--sheet': this.resolvedVariant() === 'sheet',
10015
10142
  }), ...(ngDevMode ? [{ debugName: "panelClass" }] : /* istanbul ignore next */ []));
10016
10143
  /** Exit keyframe names — one per variant — used to ignore bubbled child animationend events. */
10017
10144
  static EXIT_ANIMS = new Set(['fly-drawer-out', 'fly-drawer-sheet-out']);
@@ -10119,11 +10246,11 @@ class FlyDrawerComponent {
10119
10246
  }
10120
10247
  }
10121
10248
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyDrawerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10122
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyDrawerComponent, isStandalone: true, selector: "fly-drawer", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null }, side: { classPropertyName: "side", publicName: "side", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, dismissOnScrim: { classPropertyName: "dismissOnScrim", publicName: "dismissOnScrim", isSignal: true, isRequired: false, transformFunction: null }, dismissOnEscape: { classPropertyName: "dismissOnEscape", publicName: "dismissOnEscape", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, hideCloseButton: { classPropertyName: "hideCloseButton", publicName: "hideCloseButton", isSignal: true, isRequired: false, transformFunction: null }, bodyPadding: { classPropertyName: "bodyPadding", publicName: "bodyPadding", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", openChange: "openChange" }, host: { listeners: { "document:keydown.escape": "onEscape()" }, properties: { "class.fly-drawer--fixed": "position() === 'fixed'" } }, ngImport: i0, template: "<!--\r\n Windowed overlay drawer. Renders only while mounted (open or sliding out).\r\n Scrim + panel are absolutely positioned within the nearest positioned\r\n ancestor (the consuming app root must be position:relative).\r\n-->\r\n@if (rendered()) {\r\n <div\r\n class=\"fly-drawer__scrim\"\r\n [class.fly-drawer__scrim--leaving]=\"leaving()\"\r\n (click)=\"onScrimClick()\"\r\n aria-hidden=\"true\"\r\n ></div>\r\n\r\n <div\r\n class=\"fly-drawer__panel\"\r\n [ngClass]=\"panelClass()\"\r\n [class.fly-drawer__panel--leaving]=\"leaving()\"\r\n [style.--fly-drawer-width]=\"width()\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n [attr.aria-labelledby]=\"labelledBy()\"\r\n [attr.aria-label]=\"!labelledBy() && ariaLabel() ? (ariaLabel()! | translate) : null\"\r\n cdkTrapFocus\r\n (animationend)=\"onPanelAnimationEnd($event.animationName)\"\r\n >\r\n <!-- Header: custom slot, else heading + close \u2715. -->\r\n <ng-content select=\"[flyDrawerHeader]\">\r\n @if (heading()) {\r\n <header class=\"fly-drawer__header\">\r\n <h2 class=\"fly-drawer__title\" [id]=\"headingId\">{{ heading()! | translate }}</h2>\r\n @if (!hideCloseButton()) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-drawer__close\"\r\n (click)=\"requestClose()\"\r\n [attr.aria-label]=\"'common.action.close' | translate\"\r\n >\r\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n </header>\r\n }\r\n </ng-content>\r\n\r\n <div class=\"fly-drawer__body\" [ngClass]=\"'fly-drawer__body--pad-' + bodyPadding()\">\r\n <ng-content></ng-content>\r\n </div>\r\n\r\n <ng-content select=\"[flyDrawerFooter]\"></ng-content>\r\n </div>\r\n}\r\n", styles: [":host{position:absolute;inset:0;z-index:60;pointer-events:none}:host(.fly-drawer--fixed){position:fixed;block-size:100dvh;z-index:var(--z-overlay, 100)}.fly-drawer__scrim{position:absolute;inset:0;pointer-events:auto;background:#00000057;opacity:1;animation:fly-drawer-scrim-in .2s var(--nova-ease-micro) both}.fly-drawer__scrim--leaving{animation-name:fly-drawer-scrim-out;animation-duration:.2s}.fly-drawer__panel{position:absolute;inset-block:0;inset-inline-end:0;pointer-events:auto;display:flex;flex-direction:column;min-block-size:0;inline-size:var(--fly-drawer-width, min(480px, 100%));max-inline-size:100%;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(var(--mat-panel),var(--mat-panel));--nova-glass-drawer-shadow-x: -30px;box-shadow:var(--nova-glass-drawer-shadow-x) 0 70px #00000057,0 4px 10px #0000001a,var(--glass2-inset);-webkit-backdrop-filter:var(--glass2-blur);backdrop-filter:var(--glass2-blur)}.fly-drawer__panel:dir(rtl){--nova-glass-drawer-shadow-x: 30px}@media(prefers-reduced-transparency:reduce){.fly-drawer__panel{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.fly-drawer__panel:before,.fly-drawer__panel:after{display:none}}@media(prefers-contrast:more){.fly-drawer__panel{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.fly-drawer__panel:after{animation:none}}.fly-drawer__panel{border-start-start-radius:20px;border-end-start-radius:20px;animation:drawerIn .34s var(--nova-ease-structural) both;will-change:transform}.fly-drawer__panel--sm{inline-size:var(--fly-drawer-width, min(360px, 100%))}.fly-drawer__panel--md{inline-size:var(--fly-drawer-width, min(480px, 100%))}.fly-drawer__panel--lg{inline-size:var(--fly-drawer-width, min(640px, 100%))}.fly-drawer__panel--xl{inline-size:var(--fly-drawer-width, min(960px, 94%))}.fly-drawer__panel--side-start{--nova-drawer-in-x: -28px;--nova-glass-drawer-shadow-x: 30px;border-start-start-radius:0;border-end-start-radius:0;border-start-end-radius:20px;border-end-end-radius:20px}.fly-drawer__panel--side-start:dir(rtl){--nova-drawer-in-x: 28px;--nova-glass-drawer-shadow-x: -30px}.fly-drawer__panel--leaving{animation-name:fly-drawer-out;animation-duration:.24s;animation-timing-function:var(--nova-ease-structural)}.fly-drawer__panel--sheet{inset-inline:0;inset-block:auto 0;inline-size:100%;max-inline-size:100%;block-size:auto;max-block-size:min(85vh,720px);border-start-start-radius:20px;border-start-end-radius:20px;border-end-start-radius:0;border-end-end-radius:0;box-shadow:0 -16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset);animation-name:fly-drawer-sheet-in}.fly-drawer__panel--sheet.fly-drawer__panel--leaving{animation-name:fly-drawer-sheet-out;animation-duration:.24s;animation-timing-function:var(--nova-ease-structural)}@keyframes fly-drawer-out{0%{opacity:1;transform:translateZ(0)}to{opacity:0;transform:translate3d(var(--nova-drawer-in-x),0,0)}}@keyframes fly-drawer-sheet-in{0%{opacity:0;transform:translateY(100%)}to{opacity:1;transform:translateY(0)}}@keyframes fly-drawer-sheet-out{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(100%)}}@keyframes fly-drawer-scrim-in{0%{opacity:0}to{opacity:1}}@keyframes fly-drawer-scrim-out{0%{opacity:1}to{opacity:0}}.fly-drawer__header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 18px;border-block-end:1px solid var(--w08);flex:0 0 auto}.fly-drawer__title{margin:0;font:var(--nova-font-drawer-title);letter-spacing:var(--nova-tracking-drawer-title);color:var(--w95)}.fly-drawer__close{display:inline-flex;align-items:center;justify-content:center;inline-size:30px;block-size:30px;border:none;border-radius:9px;background:transparent;color:var(--chrome-ink);cursor:pointer}.fly-drawer__close:hover{background:var(--w1)}.fly-drawer__body{flex:1 1 auto;min-block-size:0;overflow-y:auto;padding:var(--fly-drawer-body-padding, 16px 18px);container-type:inline-size}.fly-drawer__body--pad-none{--fly-drawer-body-padding: 0}.fly-drawer__body--pad-sm{--fly-drawer-body-padding: 12px 14px}.fly-drawer__body--pad-md{--fly-drawer-body-padding: 16px 18px}.fly-drawer__body--pad-lg{--fly-drawer-body-padding: 20px 22px}:host ::ng-deep [flyDrawerFooter]{flex:0 0 auto;display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:12px 18px;border-block-start:1px solid var(--w08);background:var(--w03)}@media(prefers-reduced-motion:reduce){.fly-drawer__scrim,.fly-drawer__panel,.fly-drawer__scrim--leaving,.fly-drawer__panel--leaving{animation:none}}@media(forced-colors:active){.fly-drawer__panel{border:1px solid CanvasText}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i2.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10249
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyDrawerComponent, isStandalone: true, selector: "fly-drawer", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null }, side: { classPropertyName: "side", publicName: "side", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, dismissOnScrim: { classPropertyName: "dismissOnScrim", publicName: "dismissOnScrim", isSignal: true, isRequired: false, transformFunction: null }, dismissOnEscape: { classPropertyName: "dismissOnEscape", publicName: "dismissOnEscape", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, hideCloseButton: { classPropertyName: "hideCloseButton", publicName: "hideCloseButton", isSignal: true, isRequired: false, transformFunction: null }, bodyPadding: { classPropertyName: "bodyPadding", publicName: "bodyPadding", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", openChange: "openChange" }, host: { listeners: { "document:keydown.escape": "onEscape()" }, properties: { "class.fly-drawer--fixed": "position() === 'fixed'" } }, ngImport: i0, template: "<!--\r\n Windowed overlay drawer. Renders only while mounted (open or sliding out).\r\n Scrim + panel are absolutely positioned within the nearest positioned\r\n ancestor (the consuming app root must be position:relative).\r\n-->\r\n@if (rendered()) {\r\n <div\r\n class=\"fly-drawer__scrim\"\r\n [class.fly-drawer__scrim--leaving]=\"leaving()\"\r\n (click)=\"onScrimClick()\"\r\n aria-hidden=\"true\"\r\n ></div>\r\n\r\n <div\r\n class=\"fly-drawer__panel\"\r\n [ngClass]=\"panelClass()\"\r\n [class.fly-drawer__panel--leaving]=\"leaving()\"\r\n [style.--fly-drawer-width]=\"width()\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n [attr.aria-labelledby]=\"labelledBy()\"\r\n [attr.aria-label]=\"!labelledBy() && ariaLabel() ? (ariaLabel()! | translate) : null\"\r\n cdkTrapFocus\r\n (animationend)=\"onPanelAnimationEnd($event.animationName)\"\r\n >\r\n <!-- Header: custom slot, else heading + close \u2715. -->\r\n <ng-content select=\"[flyDrawerHeader]\">\r\n @if (heading()) {\r\n <header class=\"fly-drawer__header\">\r\n <h2 class=\"fly-drawer__title\" [id]=\"headingId\">{{ heading()! | translate }}</h2>\r\n @if (!hideCloseButton()) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-drawer__close\"\r\n (click)=\"requestClose()\"\r\n [attr.aria-label]=\"'common.action.close' | translate\"\r\n >\r\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n </header>\r\n }\r\n </ng-content>\r\n\r\n <div class=\"fly-drawer__body\" [ngClass]=\"'fly-drawer__body--pad-' + bodyPadding()\">\r\n <ng-content></ng-content>\r\n </div>\r\n\r\n <ng-content select=\"[flyDrawerFooter]\"></ng-content>\r\n </div>\r\n}\r\n", styles: [":host{position:absolute;inset:0;z-index:60;pointer-events:none;--fly-drawer-sheet-safe-inset: 0px}:host(.fly-drawer--fixed){position:fixed;block-size:100dvh;z-index:var(--z-overlay, 100);--fly-drawer-sheet-safe-inset: env(safe-area-inset-bottom, 0px)}.fly-drawer__scrim{position:absolute;inset:0;pointer-events:auto;background:#00000057;opacity:1;animation:fly-drawer-scrim-in .2s var(--nova-ease-micro) both}.fly-drawer__scrim--leaving{animation-name:fly-drawer-scrim-out;animation-duration:.2s}.fly-drawer__panel{position:absolute;inset-block:0;inset-inline-end:0;pointer-events:auto;display:flex;flex-direction:column;min-block-size:0;inline-size:var(--fly-drawer-width, min(480px, 100%));max-inline-size:100%;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(var(--mat-panel),var(--mat-panel));--nova-glass-drawer-shadow-x: -30px;box-shadow:var(--nova-glass-drawer-shadow-x) 0 70px #00000057,0 4px 10px #0000001a,var(--glass2-inset);-webkit-backdrop-filter:var(--glass2-blur);backdrop-filter:var(--glass2-blur)}.fly-drawer__panel:dir(rtl){--nova-glass-drawer-shadow-x: 30px}@media(prefers-reduced-transparency:reduce){.fly-drawer__panel{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.fly-drawer__panel:before,.fly-drawer__panel:after{display:none}}@media(prefers-contrast:more){.fly-drawer__panel{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.fly-drawer__panel:after{animation:none}}.fly-drawer__panel{border-start-start-radius:20px;border-end-start-radius:20px;animation:drawerIn .34s var(--nova-ease-structural) both;will-change:transform}.fly-drawer__panel--sm{inline-size:var(--fly-drawer-width, min(360px, 100%))}.fly-drawer__panel--md{inline-size:var(--fly-drawer-width, min(480px, 100%))}.fly-drawer__panel--lg{inline-size:var(--fly-drawer-width, min(640px, 100%))}.fly-drawer__panel--xl{inline-size:var(--fly-drawer-width, min(960px, 94%))}.fly-drawer__panel--side-start{--nova-drawer-in-x: -28px;--nova-glass-drawer-shadow-x: 30px;border-start-start-radius:0;border-end-start-radius:0;border-start-end-radius:20px;border-end-end-radius:20px}.fly-drawer__panel--side-start:dir(rtl){--nova-drawer-in-x: 28px;--nova-glass-drawer-shadow-x: -30px}.fly-drawer__panel--leaving{animation-name:fly-drawer-out;animation-duration:.24s;animation-timing-function:var(--nova-ease-structural)}.fly-drawer__panel--sheet{inset-inline:0;inset-block:auto 0;inline-size:100%;max-inline-size:100%;block-size:auto;max-block-size:min(85%,85dvh,720px);border-start-start-radius:20px;border-start-end-radius:20px;border-end-start-radius:0;border-end-end-radius:0;padding-block-end:var(--fly-drawer-sheet-safe-inset);box-shadow:0 -16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset);animation-name:fly-drawer-sheet-in}.fly-drawer__panel--sheet.fly-drawer__panel--leaving{animation-name:fly-drawer-sheet-out;animation-duration:.24s;animation-timing-function:var(--nova-ease-structural)}@keyframes fly-drawer-out{0%{opacity:1;transform:translateZ(0)}to{opacity:0;transform:translate3d(var(--nova-drawer-in-x),0,0)}}@keyframes fly-drawer-sheet-in{0%{opacity:0;transform:translateY(100%)}to{opacity:1;transform:translateY(0)}}@keyframes fly-drawer-sheet-out{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(100%)}}@keyframes fly-drawer-scrim-in{0%{opacity:0}to{opacity:1}}@keyframes fly-drawer-scrim-out{0%{opacity:1}to{opacity:0}}.fly-drawer__header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 18px;border-block-end:1px solid var(--w08);flex:0 0 auto}.fly-drawer__title{margin:0;font:var(--nova-font-drawer-title);letter-spacing:var(--nova-tracking-drawer-title);color:var(--w95)}.fly-drawer__close{display:inline-flex;align-items:center;justify-content:center;inline-size:30px;block-size:30px;border:none;border-radius:9px;background:transparent;color:var(--chrome-ink);cursor:pointer}.fly-drawer__close:hover{background:var(--w1)}@media(pointer:coarse){.fly-drawer__close{inline-size:44px;block-size:44px}}.fly-drawer__body{flex:1 1 auto;min-block-size:0;overflow-y:auto;padding:var(--fly-drawer-body-padding, 16px 18px);container-type:inline-size}.fly-drawer__body--pad-none{--fly-drawer-body-padding: 0}.fly-drawer__body--pad-sm{--fly-drawer-body-padding: 12px 14px}.fly-drawer__body--pad-md{--fly-drawer-body-padding: 16px 18px}.fly-drawer__body--pad-lg{--fly-drawer-body-padding: 20px 22px}:host ::ng-deep [flyDrawerFooter]{flex:0 0 auto;display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:12px 18px;border-block-start:1px solid var(--w08);background:var(--w03)}@media(prefers-reduced-motion:reduce){.fly-drawer__scrim,.fly-drawer__panel,.fly-drawer__scrim--leaving,.fly-drawer__panel--leaving{animation:none}}@media(forced-colors:active){.fly-drawer__panel{border:1px solid CanvasText}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i2.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10123
10250
  }
10124
10251
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyDrawerComponent, decorators: [{
10125
10252
  type: Component,
10126
- args: [{ selector: 'fly-drawer', standalone: true, imports: [CommonModule, A11yModule, TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, host: { '[class.fly-drawer--fixed]': "position() === 'fixed'" }, template: "<!--\r\n Windowed overlay drawer. Renders only while mounted (open or sliding out).\r\n Scrim + panel are absolutely positioned within the nearest positioned\r\n ancestor (the consuming app root must be position:relative).\r\n-->\r\n@if (rendered()) {\r\n <div\r\n class=\"fly-drawer__scrim\"\r\n [class.fly-drawer__scrim--leaving]=\"leaving()\"\r\n (click)=\"onScrimClick()\"\r\n aria-hidden=\"true\"\r\n ></div>\r\n\r\n <div\r\n class=\"fly-drawer__panel\"\r\n [ngClass]=\"panelClass()\"\r\n [class.fly-drawer__panel--leaving]=\"leaving()\"\r\n [style.--fly-drawer-width]=\"width()\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n [attr.aria-labelledby]=\"labelledBy()\"\r\n [attr.aria-label]=\"!labelledBy() && ariaLabel() ? (ariaLabel()! | translate) : null\"\r\n cdkTrapFocus\r\n (animationend)=\"onPanelAnimationEnd($event.animationName)\"\r\n >\r\n <!-- Header: custom slot, else heading + close \u2715. -->\r\n <ng-content select=\"[flyDrawerHeader]\">\r\n @if (heading()) {\r\n <header class=\"fly-drawer__header\">\r\n <h2 class=\"fly-drawer__title\" [id]=\"headingId\">{{ heading()! | translate }}</h2>\r\n @if (!hideCloseButton()) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-drawer__close\"\r\n (click)=\"requestClose()\"\r\n [attr.aria-label]=\"'common.action.close' | translate\"\r\n >\r\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n </header>\r\n }\r\n </ng-content>\r\n\r\n <div class=\"fly-drawer__body\" [ngClass]=\"'fly-drawer__body--pad-' + bodyPadding()\">\r\n <ng-content></ng-content>\r\n </div>\r\n\r\n <ng-content select=\"[flyDrawerFooter]\"></ng-content>\r\n </div>\r\n}\r\n", styles: [":host{position:absolute;inset:0;z-index:60;pointer-events:none}:host(.fly-drawer--fixed){position:fixed;block-size:100dvh;z-index:var(--z-overlay, 100)}.fly-drawer__scrim{position:absolute;inset:0;pointer-events:auto;background:#00000057;opacity:1;animation:fly-drawer-scrim-in .2s var(--nova-ease-micro) both}.fly-drawer__scrim--leaving{animation-name:fly-drawer-scrim-out;animation-duration:.2s}.fly-drawer__panel{position:absolute;inset-block:0;inset-inline-end:0;pointer-events:auto;display:flex;flex-direction:column;min-block-size:0;inline-size:var(--fly-drawer-width, min(480px, 100%));max-inline-size:100%;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(var(--mat-panel),var(--mat-panel));--nova-glass-drawer-shadow-x: -30px;box-shadow:var(--nova-glass-drawer-shadow-x) 0 70px #00000057,0 4px 10px #0000001a,var(--glass2-inset);-webkit-backdrop-filter:var(--glass2-blur);backdrop-filter:var(--glass2-blur)}.fly-drawer__panel:dir(rtl){--nova-glass-drawer-shadow-x: 30px}@media(prefers-reduced-transparency:reduce){.fly-drawer__panel{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.fly-drawer__panel:before,.fly-drawer__panel:after{display:none}}@media(prefers-contrast:more){.fly-drawer__panel{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.fly-drawer__panel:after{animation:none}}.fly-drawer__panel{border-start-start-radius:20px;border-end-start-radius:20px;animation:drawerIn .34s var(--nova-ease-structural) both;will-change:transform}.fly-drawer__panel--sm{inline-size:var(--fly-drawer-width, min(360px, 100%))}.fly-drawer__panel--md{inline-size:var(--fly-drawer-width, min(480px, 100%))}.fly-drawer__panel--lg{inline-size:var(--fly-drawer-width, min(640px, 100%))}.fly-drawer__panel--xl{inline-size:var(--fly-drawer-width, min(960px, 94%))}.fly-drawer__panel--side-start{--nova-drawer-in-x: -28px;--nova-glass-drawer-shadow-x: 30px;border-start-start-radius:0;border-end-start-radius:0;border-start-end-radius:20px;border-end-end-radius:20px}.fly-drawer__panel--side-start:dir(rtl){--nova-drawer-in-x: 28px;--nova-glass-drawer-shadow-x: -30px}.fly-drawer__panel--leaving{animation-name:fly-drawer-out;animation-duration:.24s;animation-timing-function:var(--nova-ease-structural)}.fly-drawer__panel--sheet{inset-inline:0;inset-block:auto 0;inline-size:100%;max-inline-size:100%;block-size:auto;max-block-size:min(85vh,720px);border-start-start-radius:20px;border-start-end-radius:20px;border-end-start-radius:0;border-end-end-radius:0;box-shadow:0 -16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset);animation-name:fly-drawer-sheet-in}.fly-drawer__panel--sheet.fly-drawer__panel--leaving{animation-name:fly-drawer-sheet-out;animation-duration:.24s;animation-timing-function:var(--nova-ease-structural)}@keyframes fly-drawer-out{0%{opacity:1;transform:translateZ(0)}to{opacity:0;transform:translate3d(var(--nova-drawer-in-x),0,0)}}@keyframes fly-drawer-sheet-in{0%{opacity:0;transform:translateY(100%)}to{opacity:1;transform:translateY(0)}}@keyframes fly-drawer-sheet-out{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(100%)}}@keyframes fly-drawer-scrim-in{0%{opacity:0}to{opacity:1}}@keyframes fly-drawer-scrim-out{0%{opacity:1}to{opacity:0}}.fly-drawer__header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 18px;border-block-end:1px solid var(--w08);flex:0 0 auto}.fly-drawer__title{margin:0;font:var(--nova-font-drawer-title);letter-spacing:var(--nova-tracking-drawer-title);color:var(--w95)}.fly-drawer__close{display:inline-flex;align-items:center;justify-content:center;inline-size:30px;block-size:30px;border:none;border-radius:9px;background:transparent;color:var(--chrome-ink);cursor:pointer}.fly-drawer__close:hover{background:var(--w1)}.fly-drawer__body{flex:1 1 auto;min-block-size:0;overflow-y:auto;padding:var(--fly-drawer-body-padding, 16px 18px);container-type:inline-size}.fly-drawer__body--pad-none{--fly-drawer-body-padding: 0}.fly-drawer__body--pad-sm{--fly-drawer-body-padding: 12px 14px}.fly-drawer__body--pad-md{--fly-drawer-body-padding: 16px 18px}.fly-drawer__body--pad-lg{--fly-drawer-body-padding: 20px 22px}:host ::ng-deep [flyDrawerFooter]{flex:0 0 auto;display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:12px 18px;border-block-start:1px solid var(--w08);background:var(--w03)}@media(prefers-reduced-motion:reduce){.fly-drawer__scrim,.fly-drawer__panel,.fly-drawer__scrim--leaving,.fly-drawer__panel--leaving{animation:none}}@media(forced-colors:active){.fly-drawer__panel{border:1px solid CanvasText}}\n"] }]
10253
+ args: [{ selector: 'fly-drawer', standalone: true, imports: [CommonModule, A11yModule, TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, host: { '[class.fly-drawer--fixed]': "position() === 'fixed'" }, template: "<!--\r\n Windowed overlay drawer. Renders only while mounted (open or sliding out).\r\n Scrim + panel are absolutely positioned within the nearest positioned\r\n ancestor (the consuming app root must be position:relative).\r\n-->\r\n@if (rendered()) {\r\n <div\r\n class=\"fly-drawer__scrim\"\r\n [class.fly-drawer__scrim--leaving]=\"leaving()\"\r\n (click)=\"onScrimClick()\"\r\n aria-hidden=\"true\"\r\n ></div>\r\n\r\n <div\r\n class=\"fly-drawer__panel\"\r\n [ngClass]=\"panelClass()\"\r\n [class.fly-drawer__panel--leaving]=\"leaving()\"\r\n [style.--fly-drawer-width]=\"width()\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n [attr.aria-labelledby]=\"labelledBy()\"\r\n [attr.aria-label]=\"!labelledBy() && ariaLabel() ? (ariaLabel()! | translate) : null\"\r\n cdkTrapFocus\r\n (animationend)=\"onPanelAnimationEnd($event.animationName)\"\r\n >\r\n <!-- Header: custom slot, else heading + close \u2715. -->\r\n <ng-content select=\"[flyDrawerHeader]\">\r\n @if (heading()) {\r\n <header class=\"fly-drawer__header\">\r\n <h2 class=\"fly-drawer__title\" [id]=\"headingId\">{{ heading()! | translate }}</h2>\r\n @if (!hideCloseButton()) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-drawer__close\"\r\n (click)=\"requestClose()\"\r\n [attr.aria-label]=\"'common.action.close' | translate\"\r\n >\r\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\r\n </button>\r\n }\r\n </header>\r\n }\r\n </ng-content>\r\n\r\n <div class=\"fly-drawer__body\" [ngClass]=\"'fly-drawer__body--pad-' + bodyPadding()\">\r\n <ng-content></ng-content>\r\n </div>\r\n\r\n <ng-content select=\"[flyDrawerFooter]\"></ng-content>\r\n </div>\r\n}\r\n", styles: [":host{position:absolute;inset:0;z-index:60;pointer-events:none;--fly-drawer-sheet-safe-inset: 0px}:host(.fly-drawer--fixed){position:fixed;block-size:100dvh;z-index:var(--z-overlay, 100);--fly-drawer-sheet-safe-inset: env(safe-area-inset-bottom, 0px)}.fly-drawer__scrim{position:absolute;inset:0;pointer-events:auto;background:#00000057;opacity:1;animation:fly-drawer-scrim-in .2s var(--nova-ease-micro) both}.fly-drawer__scrim--leaving{animation-name:fly-drawer-scrim-out;animation-duration:.2s}.fly-drawer__panel{position:absolute;inset-block:0;inset-inline-end:0;pointer-events:auto;display:flex;flex-direction:column;min-block-size:0;inline-size:var(--fly-drawer-width, min(480px, 100%));max-inline-size:100%;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(var(--mat-panel),var(--mat-panel));--nova-glass-drawer-shadow-x: -30px;box-shadow:var(--nova-glass-drawer-shadow-x) 0 70px #00000057,0 4px 10px #0000001a,var(--glass2-inset);-webkit-backdrop-filter:var(--glass2-blur);backdrop-filter:var(--glass2-blur)}.fly-drawer__panel:dir(rtl){--nova-glass-drawer-shadow-x: 30px}@media(prefers-reduced-transparency:reduce){.fly-drawer__panel{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.fly-drawer__panel:before,.fly-drawer__panel:after{display:none}}@media(prefers-contrast:more){.fly-drawer__panel{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.fly-drawer__panel:after{animation:none}}.fly-drawer__panel{border-start-start-radius:20px;border-end-start-radius:20px;animation:drawerIn .34s var(--nova-ease-structural) both;will-change:transform}.fly-drawer__panel--sm{inline-size:var(--fly-drawer-width, min(360px, 100%))}.fly-drawer__panel--md{inline-size:var(--fly-drawer-width, min(480px, 100%))}.fly-drawer__panel--lg{inline-size:var(--fly-drawer-width, min(640px, 100%))}.fly-drawer__panel--xl{inline-size:var(--fly-drawer-width, min(960px, 94%))}.fly-drawer__panel--side-start{--nova-drawer-in-x: -28px;--nova-glass-drawer-shadow-x: 30px;border-start-start-radius:0;border-end-start-radius:0;border-start-end-radius:20px;border-end-end-radius:20px}.fly-drawer__panel--side-start:dir(rtl){--nova-drawer-in-x: 28px;--nova-glass-drawer-shadow-x: -30px}.fly-drawer__panel--leaving{animation-name:fly-drawer-out;animation-duration:.24s;animation-timing-function:var(--nova-ease-structural)}.fly-drawer__panel--sheet{inset-inline:0;inset-block:auto 0;inline-size:100%;max-inline-size:100%;block-size:auto;max-block-size:min(85%,85dvh,720px);border-start-start-radius:20px;border-start-end-radius:20px;border-end-start-radius:0;border-end-end-radius:0;padding-block-end:var(--fly-drawer-sheet-safe-inset);box-shadow:0 -16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset);animation-name:fly-drawer-sheet-in}.fly-drawer__panel--sheet.fly-drawer__panel--leaving{animation-name:fly-drawer-sheet-out;animation-duration:.24s;animation-timing-function:var(--nova-ease-structural)}@keyframes fly-drawer-out{0%{opacity:1;transform:translateZ(0)}to{opacity:0;transform:translate3d(var(--nova-drawer-in-x),0,0)}}@keyframes fly-drawer-sheet-in{0%{opacity:0;transform:translateY(100%)}to{opacity:1;transform:translateY(0)}}@keyframes fly-drawer-sheet-out{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(100%)}}@keyframes fly-drawer-scrim-in{0%{opacity:0}to{opacity:1}}@keyframes fly-drawer-scrim-out{0%{opacity:1}to{opacity:0}}.fly-drawer__header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 18px;border-block-end:1px solid var(--w08);flex:0 0 auto}.fly-drawer__title{margin:0;font:var(--nova-font-drawer-title);letter-spacing:var(--nova-tracking-drawer-title);color:var(--w95)}.fly-drawer__close{display:inline-flex;align-items:center;justify-content:center;inline-size:30px;block-size:30px;border:none;border-radius:9px;background:transparent;color:var(--chrome-ink);cursor:pointer}.fly-drawer__close:hover{background:var(--w1)}@media(pointer:coarse){.fly-drawer__close{inline-size:44px;block-size:44px}}.fly-drawer__body{flex:1 1 auto;min-block-size:0;overflow-y:auto;padding:var(--fly-drawer-body-padding, 16px 18px);container-type:inline-size}.fly-drawer__body--pad-none{--fly-drawer-body-padding: 0}.fly-drawer__body--pad-sm{--fly-drawer-body-padding: 12px 14px}.fly-drawer__body--pad-md{--fly-drawer-body-padding: 16px 18px}.fly-drawer__body--pad-lg{--fly-drawer-body-padding: 20px 22px}:host ::ng-deep [flyDrawerFooter]{flex:0 0 auto;display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:12px 18px;border-block-start:1px solid var(--w08);background:var(--w03)}@media(prefers-reduced-motion:reduce){.fly-drawer__scrim,.fly-drawer__panel,.fly-drawer__scrim--leaving,.fly-drawer__panel--leaving{animation:none}}@media(forced-colors:active){.fly-drawer__panel{border:1px solid CanvasText}}\n"] }]
10127
10254
  }], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }], side: [{ type: i0.Input, args: [{ isSignal: true, alias: "side", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], dismissOnScrim: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissOnScrim", required: false }] }], dismissOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissOnEscape", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], hideCloseButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideCloseButton", required: false }] }], bodyPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "bodyPadding", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], openChange: [{ type: i0.Output, args: ["openChange"] }], onEscape: [{
10128
10255
  type: HostListener,
10129
10256
  args: ['document:keydown.escape']
@@ -11037,7 +11164,7 @@ class FlySelectComponent {
11037
11164
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlySelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11038
11165
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlySelectComponent, isStandalone: true, selector: "fly-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, showSelectAll: { classPropertyName: "showSelectAll", publicName: "showSelectAll", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", 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 }, noResultsLabel: { classPropertyName: "noResultsLabel", publicName: "noResultsLabel", isSignal: true, isRequired: false, transformFunction: null }, selectAllLabel: { classPropertyName: "selectAllLabel", publicName: "selectAllLabel", isSignal: true, isRequired: false, transformFunction: null }, deselectAllLabel: { classPropertyName: "deselectAllLabel", publicName: "deselectAllLabel", isSignal: true, isRequired: false, transformFunction: null }, clearLabel: { classPropertyName: "clearLabel", publicName: "clearLabel", isSignal: true, isRequired: false, transformFunction: null }, summaryFormatter: { classPropertyName: "summaryFormatter", publicName: "summaryFormatter", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange", openedChange: "openedChange" }, host: { properties: { "class.fly-select--open": "isOpen()", "class.fly-select--disabled": "effectiveDisabled()" }, classAttribute: "fly-select" }, providers: [
11039
11166
  { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => FlySelectComponent), multi: true },
11040
- ], viewQueries: [{ propertyName: "panelEl", first: true, predicate: ["panelRef"], descendants: true, isSignal: true }, { propertyName: "searchEl", first: true, predicate: ["searchRef"], descendants: true }, { propertyName: "triggerEl", first: true, predicate: ["triggerRef"], descendants: true }], ngImport: i0, template: "<!-- \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\u2500\u2500 -->\r\n<button\r\n #triggerRef\r\n type=\"button\"\r\n [id]=\"triggerId\"\r\n class=\"fly-select__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() || null\"\r\n [class.fly-select__trigger--placeholder]=\"!hasSelection()\"\r\n [disabled]=\"effectiveDisabled()\"\r\n (click)=\"toggleOpen()\"\r\n (keydown)=\"onTriggerKeydown($event)\"\r\n>\r\n <span class=\"fly-select__trigger-label\">{{ triggerLabel() }}</span>\r\n\r\n @if (clearable() && hasSelection() && !effectiveDisabled()) {\r\n <span\r\n class=\"fly-select__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-select__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\u2500\u2500 -->\r\n<!-- Portaled to <body> by the component (see class docblock) \u2014 position: fixed, computed from\r\n the trigger. [style.*] bindings null out the unused axis (top XOR bottom, left XOR right)\r\n rather than leaving a stale 0, which is what lets the flip-above / RTL cases each win\r\n outright instead of fighting a leftover declaration from the other branch. -->\r\n@if (isOpen()) {\r\n <div\r\n #panelRef\r\n class=\"fly-select__panel\"\r\n [class.fly-select__panel--flip]=\"panelPosition()?.flipAbove\"\r\n [style.top.px]=\"panelPosition()?.top\"\r\n [style.bottom.px]=\"panelPosition()?.bottom\"\r\n [style.left.px]=\"panelPosition()?.left\"\r\n [style.right.px]=\"panelPosition()?.right\"\r\n [style.min-width.px]=\"panelPosition()?.minWidth\"\r\n [style.max-height.px]=\"panelPosition()?.maxHeight\"\r\n tabindex=\"-1\"\r\n (keydown)=\"onPanelKeydown($event)\"\r\n >\r\n @if (searchable()) {\r\n <div class=\"fly-select__search\">\r\n <input\r\n #searchRef\r\n type=\"text\"\r\n class=\"fly-select__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\r\n @if (multiple() && showSelectAll() && navigableOptions().length > 0) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-select__select-all\"\r\n (click)=\"toggleSelectAll()\"\r\n >\r\n {{ allVisibleSelected() ? deselectAllText() : selectAllText() }}\r\n </button>\r\n }\r\n\r\n <div\r\n class=\"fly-select__listbox\"\r\n role=\"listbox\"\r\n [id]=\"listboxId\"\r\n [attr.tabindex]=\"searchable() ? null : 0\"\r\n [attr.aria-multiselectable]=\"multiple() ? true : null\"\r\n [attr.aria-activedescendant]=\"activeDescendant() || null\"\r\n [attr.aria-label]=\"ariaLabel() || null\"\r\n >\r\n @for (group of renderGroups(); track group.key) {\r\n @if (group.key !== null) {\r\n <div class=\"fly-select__group-label\" role=\"presentation\">{{ group.key }}</div>\r\n }\r\n @for (opt of group.options; track opt.value) {\r\n <div\r\n class=\"fly-select__option\"\r\n role=\"option\"\r\n tabindex=\"-1\"\r\n [id]=\"opt.disabled ? null : optionId(navIndexOf(opt.value))\"\r\n [attr.aria-selected]=\"isSelected(opt.value)\"\r\n [attr.aria-disabled]=\"opt.disabled ? true : null\"\r\n [class.fly-select__option--selected]=\"isSelected(opt.value)\"\r\n [class.fly-select__option--active]=\"!opt.disabled && navIndexOf(opt.value) === activeNavIndex()\"\r\n [class.fly-select__option--disabled]=\"opt.disabled\"\r\n (click)=\"onOptionClick(opt)\"\r\n (keydown.enter)=\"onOptionClick(opt)\"\r\n (mouseenter)=\"onOptionHover(navIndexOf(opt.value))\"\r\n >\r\n @if (multiple()) {\r\n <span class=\"fly-select__checkbox\" aria-hidden=\"true\">\r\n @if (isSelected(opt.value)) { <span class=\"fly-select__checkbox-tick\">\u2713</span> }\r\n </span>\r\n } @else {\r\n <span class=\"fly-select__check\" aria-hidden=\"true\">\r\n @if (isSelected(opt.value)) { \u2713 }\r\n </span>\r\n }\r\n <span class=\"fly-select__option-label\">{{ opt.label }}</span>\r\n </div>\r\n }\r\n }\r\n\r\n @if (filteredOptions().length === 0) {\r\n <div class=\"fly-select__empty\" role=\"presentation\">{{ noResultsText() }}</div>\r\n }\r\n </div>\r\n </div>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after,.fly-select__panel,.fly-select__panel *,.fly-select__panel *:before,.fly-select__panel *:after{box-sizing:border-box}:host{display:inline-block;inline-size:100%;min-inline-size:12rem}.fly-select__trigger{display:flex;align-items:center;gap:8px;inline-size:100%;block-size:36px;padding-inline:11px;border:1px solid var(--w14);border-radius:10px;background:var(--w05);color:var(--w85);font:500 13px/1.3 var(--font-family);text-align:start;cursor:pointer;transition:border-color var(--nova-duration-hover) var(--nova-ease-micro),background var(--nova-duration-hover) var(--nova-ease-micro)}.fly-select__trigger:hover:not(:disabled){background:var(--w07)}.fly-select__trigger:focus-visible{outline:3px solid var(--accent);outline-offset:3px;border-radius:inherit}.fly-select__trigger:disabled{opacity:.38;filter:saturate(.6);cursor:not-allowed}:host(.fly-select--open) .fly-select__trigger{border-color:var(--accent);background:var(--w08)}.fly-select__trigger--placeholder .fly-select__trigger-label{color:var(--w42)}.fly-select__trigger-label{flex:1 1 auto;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.fly-select__clear{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;inline-size:18px;block-size:18px;border-radius:50%;color:var(--w42);font-size:11px;line-height:1;cursor:pointer;transition:background var(--nova-duration-hover) var(--nova-ease-micro),color var(--nova-duration-hover) var(--nova-ease-micro)}.fly-select__clear:hover{background:var(--w1);color:var(--w85)}.fly-select__chevron{flex:0 0 auto;color:var(--w45);font-size:10px;transition:transform .22s var(--nova-ease-structural)}:host(.fly-select--open) .fly-select__chevron{transform:rotate(180deg)}@media(pointer:coarse){.fly-select__trigger{min-block-size:44px}.fly-select__clear{inline-size:24px;block-size:24px}}.fly-select__panel{position:fixed;z-index:1000;display:flex;flex-direction:column;inline-size:max-content;max-inline-size:min(420px,90vw);overflow:hidden;padding:6px;border-radius:14px;animation:menuIn .24s var(--nova-ease-structural) both;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-a),var(--mat-menu-b));box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset);-webkit-backdrop-filter:var(--glass2-blur);backdrop-filter:var(--glass2-blur)}@media(prefers-reduced-transparency:reduce){.fly-select__panel{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.fly-select__panel:before,.fly-select__panel:after{display:none}}@media(prefers-contrast:more){.fly-select__panel{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.fly-select__panel:after{animation:none}}.fly-select__search{padding:6px;border-block-end:1px solid var(--w08)}.fly-select__search-input{inline-size:100%;block-size:30px;padding-inline:9px;border:1px solid var(--w14);border-radius:8px;background:var(--w05);color:var(--w85);font:500 13px/1.3 var(--font-family)}.fly-select__search-input::placeholder{color:var(--w42)}.fly-select__search-input:focus-visible{outline:none;border-color:var(--accent);background:var(--w07)}.fly-select__select-all{display:block;inline-size:100%;padding:7px 9px;border:0;border-block-end:1px solid var(--w08);background:transparent;color:var(--accent);font:600 12.5px/1.3 var(--font-family);text-align:start;cursor:pointer}.fly-select__select-all:hover{background:var(--w1)}.fly-select__listbox{flex:1 1 auto;min-block-size:0;overflow-y:auto;padding-block:4px}.fly-select__listbox:focus-visible{outline:none}.fly-select__group-label{padding:6px 9px 2px;color:var(--w42);font:var(--nova-font-eyebrow);letter-spacing:var(--nova-tracking-eyebrow);text-transform:uppercase}.fly-select__option{display:flex;align-items:center;gap:8px;min-block-size:32px;padding:7px 9px;border-radius:8px;color:var(--w85);font:500 13px/1.3 var(--font-family);cursor:pointer;transition:background var(--nova-duration-hover) var(--nova-ease-micro)}.fly-select__option--active{background:var(--w1)}.fly-select__option--selected{background:var(--w08);font-weight:600}.fly-select__option--selected.fly-select__option--active{background:var(--w08);box-shadow:inset 0 0 0 1px var(--accent)}.fly-select__option--disabled{opacity:.38;filter:saturate(.6);cursor:not-allowed;pointer-events:none}.fly-select__option-label{flex:1 1 auto;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.fly-select__check{flex:0 0 auto;inline-size:16px;color:var(--accent);font-size:14px;font-weight:600;text-align:center}.fly-select__checkbox{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;inline-size:16px;block-size:16px;border:1.5px solid var(--w22);border-radius:4px;transition:background var(--nova-duration-hover) var(--nova-ease-micro),border-color var(--nova-duration-hover) var(--nova-ease-micro)}.fly-select__option--selected .fly-select__checkbox{background:var(--accent);border-color:var(--accent)}.fly-select__checkbox-tick{color:var(--on-accent);font-size:10px;line-height:1}.fly-select__empty{padding:16px 9px;text-align:center;color:var(--w42);font:400 12.5px/1.4 var(--font-family)}@media(pointer:coarse){.fly-select__option{min-block-size:44px}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11167
+ ], viewQueries: [{ propertyName: "panelEl", first: true, predicate: ["panelRef"], descendants: true, isSignal: true }, { propertyName: "searchEl", first: true, predicate: ["searchRef"], descendants: true }, { propertyName: "triggerEl", first: true, predicate: ["triggerRef"], descendants: true }], ngImport: i0, template: "<!-- \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\u2500\u2500 -->\r\n<button\r\n #triggerRef\r\n type=\"button\"\r\n [id]=\"triggerId\"\r\n class=\"fly-select__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() || null\"\r\n [class.fly-select__trigger--placeholder]=\"!hasSelection()\"\r\n [disabled]=\"effectiveDisabled()\"\r\n (click)=\"toggleOpen()\"\r\n (keydown)=\"onTriggerKeydown($event)\"\r\n>\r\n <span class=\"fly-select__trigger-label\">{{ triggerLabel() }}</span>\r\n\r\n @if (clearable() && hasSelection() && !effectiveDisabled()) {\r\n <span\r\n class=\"fly-select__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-select__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\u2500\u2500 -->\r\n<!-- Portaled to <body> by the component (see class docblock) \u2014 position: fixed, computed from\r\n the trigger. [style.*] bindings null out the unused axis (top XOR bottom, left XOR right)\r\n rather than leaving a stale 0, which is what lets the flip-above / RTL cases each win\r\n outright instead of fighting a leftover declaration from the other branch. -->\r\n@if (isOpen()) {\r\n <div\r\n #panelRef\r\n class=\"fly-select__panel\"\r\n [class.fly-select__panel--flip]=\"panelPosition()?.flipAbove\"\r\n [style.top.px]=\"panelPosition()?.top\"\r\n [style.bottom.px]=\"panelPosition()?.bottom\"\r\n [style.left.px]=\"panelPosition()?.left\"\r\n [style.right.px]=\"panelPosition()?.right\"\r\n [style.min-width.px]=\"panelPosition()?.minWidth\"\r\n [style.max-height.px]=\"panelPosition()?.maxHeight\"\r\n tabindex=\"-1\"\r\n (keydown)=\"onPanelKeydown($event)\"\r\n >\r\n @if (searchable()) {\r\n <div class=\"fly-select__search\">\r\n <input\r\n #searchRef\r\n type=\"text\"\r\n class=\"fly-select__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\r\n @if (multiple() && showSelectAll() && navigableOptions().length > 0) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-select__select-all\"\r\n (click)=\"toggleSelectAll()\"\r\n >\r\n {{ allVisibleSelected() ? deselectAllText() : selectAllText() }}\r\n </button>\r\n }\r\n\r\n <div\r\n class=\"fly-select__listbox\"\r\n role=\"listbox\"\r\n [id]=\"listboxId\"\r\n [attr.tabindex]=\"searchable() ? null : 0\"\r\n [attr.aria-multiselectable]=\"multiple() ? true : null\"\r\n [attr.aria-activedescendant]=\"activeDescendant() || null\"\r\n [attr.aria-label]=\"ariaLabel() || null\"\r\n >\r\n @for (group of renderGroups(); track group.key) {\r\n @if (group.key !== null) {\r\n <div class=\"fly-select__group-label\" role=\"presentation\">{{ group.key }}</div>\r\n }\r\n @for (opt of group.options; track opt.value) {\r\n <div\r\n class=\"fly-select__option\"\r\n role=\"option\"\r\n tabindex=\"-1\"\r\n [id]=\"opt.disabled ? null : optionId(navIndexOf(opt.value))\"\r\n [attr.aria-selected]=\"isSelected(opt.value)\"\r\n [attr.aria-disabled]=\"opt.disabled ? true : null\"\r\n [class.fly-select__option--selected]=\"isSelected(opt.value)\"\r\n [class.fly-select__option--active]=\"!opt.disabled && navIndexOf(opt.value) === activeNavIndex()\"\r\n [class.fly-select__option--disabled]=\"opt.disabled\"\r\n (click)=\"onOptionClick(opt)\"\r\n (keydown.enter)=\"onOptionClick(opt)\"\r\n (mouseenter)=\"onOptionHover(navIndexOf(opt.value))\"\r\n >\r\n @if (multiple()) {\r\n <span class=\"fly-select__checkbox\" aria-hidden=\"true\">\r\n @if (isSelected(opt.value)) { <span class=\"fly-select__checkbox-tick\">\u2713</span> }\r\n </span>\r\n } @else {\r\n <span class=\"fly-select__check\" aria-hidden=\"true\">\r\n @if (isSelected(opt.value)) { \u2713 }\r\n </span>\r\n }\r\n <span class=\"fly-select__option-label\">{{ opt.label }}</span>\r\n </div>\r\n }\r\n }\r\n\r\n @if (filteredOptions().length === 0) {\r\n <div class=\"fly-select__empty\" role=\"presentation\">{{ noResultsText() }}</div>\r\n }\r\n </div>\r\n </div>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after,.fly-select__panel,.fly-select__panel *,.fly-select__panel *:before,.fly-select__panel *:after{box-sizing:border-box}:host{display:inline-block;inline-size:100%;min-inline-size:12rem}.fly-select__trigger{display:flex;align-items:center;gap:8px;inline-size:100%;block-size:36px;padding-inline:11px;border:1px solid var(--w14);border-radius:10px;background:var(--w05);color:var(--w85);font:500 13px/1.3 var(--font-family);text-align:start;cursor:pointer;transition:border-color var(--nova-duration-hover) var(--nova-ease-micro),background var(--nova-duration-hover) var(--nova-ease-micro)}.fly-select__trigger:hover:not(:disabled){background:var(--w07)}.fly-select__trigger:focus-visible{outline:3px solid var(--accent);outline-offset:3px;border-radius:inherit}.fly-select__trigger:disabled{opacity:.38;filter:saturate(.6);cursor:not-allowed}:host(.fly-select--open) .fly-select__trigger{border-color:var(--accent);background:var(--w08)}.fly-select__trigger--placeholder .fly-select__trigger-label{color:var(--w42)}.fly-select__trigger-label{flex:1 1 auto;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.fly-select__clear{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;inline-size:18px;block-size:18px;border-radius:50%;color:var(--w42);font-size:11px;line-height:1;cursor:pointer;transition:background var(--nova-duration-hover) var(--nova-ease-micro),color var(--nova-duration-hover) var(--nova-ease-micro)}.fly-select__clear:hover{background:var(--w1);color:var(--w85)}.fly-select__chevron{flex:0 0 auto;color:var(--w45);font-size:10px;transition:transform .22s var(--nova-ease-structural)}:host(.fly-select--open) .fly-select__chevron{transform:rotate(180deg)}@media(pointer:coarse){.fly-select__trigger{min-block-size:44px}.fly-select__clear{inline-size:24px;block-size:24px}}.fly-select__panel{position:fixed;z-index:1000;display:flex;flex-direction:column;inline-size:max-content;max-inline-size:min(420px,90vw);overflow:hidden;padding:6px;border-radius:14px;animation:menuIn .24s var(--nova-ease-structural) both;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-a),var(--mat-menu-b));box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset);-webkit-backdrop-filter:var(--glass2-blur);backdrop-filter:var(--glass2-blur)}@media(prefers-reduced-transparency:reduce){.fly-select__panel{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.fly-select__panel:before,.fly-select__panel:after{display:none}}@media(prefers-contrast:more){.fly-select__panel{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.fly-select__panel:after{animation:none}}.fly-select__search{padding:6px;border-block-end:1px solid var(--w08)}.fly-select__search-input{inline-size:100%;block-size:30px;padding-inline:9px;border:1px solid var(--w14);border-radius:8px;background:var(--w05);color:var(--w85);font:500 13px/1.3 var(--font-family)}.fly-select__search-input::placeholder{color:var(--w42)}.fly-select__search-input:focus-visible{outline:none;border-color:var(--accent);background:var(--w07)}.fly-select__select-all{display:block;inline-size:100%;padding:7px 9px;border:0;border-block-end:1px solid var(--w08);background:transparent;color:var(--accent);font:600 12.5px/1.3 var(--font-family);text-align:start;cursor:pointer}.fly-select__select-all:hover{background:var(--w1)}.fly-select__listbox{flex:1 1 auto;min-block-size:0;overflow-y:auto;padding-block:4px}.fly-select__listbox:focus-visible{outline:none}.fly-select__group-label{padding:6px 9px 2px;color:var(--w5);font:var(--nova-font-eyebrow);letter-spacing:var(--nova-tracking-eyebrow);text-transform:uppercase}.fly-select__option{display:flex;align-items:center;gap:8px;min-block-size:32px;padding:7px 9px;border-radius:8px;color:var(--w85);font:500 13px/1.3 var(--font-family);cursor:pointer;transition:background var(--nova-duration-hover) var(--nova-ease-micro)}.fly-select__option--active{background:var(--w1)}.fly-select__option--selected{background:var(--w08);font-weight:600}.fly-select__option--selected.fly-select__option--active{background:var(--w08);box-shadow:inset 0 0 0 1px var(--accent)}.fly-select__option--disabled{opacity:.38;filter:saturate(.6);cursor:not-allowed;pointer-events:none}.fly-select__option-label{flex:1 1 auto;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.fly-select__check{flex:0 0 auto;inline-size:16px;color:var(--accent);font-size:14px;font-weight:600;text-align:center}.fly-select__checkbox{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;inline-size:16px;block-size:16px;border:1.5px solid var(--w22);border-radius:4px;transition:background var(--nova-duration-hover) var(--nova-ease-micro),border-color var(--nova-duration-hover) var(--nova-ease-micro)}.fly-select__option--selected .fly-select__checkbox{background:var(--accent);border-color:var(--accent)}.fly-select__checkbox-tick{color:var(--on-accent);font-size:10px;line-height:1}.fly-select__empty{padding:16px 9px;text-align:center;color:var(--w42);font:400 12.5px/1.4 var(--font-family)}@media(pointer:coarse){.fly-select__option{min-block-size:44px}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11041
11168
  }
11042
11169
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlySelectComponent, decorators: [{
11043
11170
  type: Component,
@@ -11047,7 +11174,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
11047
11174
  class: 'fly-select',
11048
11175
  '[class.fly-select--open]': 'isOpen()',
11049
11176
  '[class.fly-select--disabled]': 'effectiveDisabled()',
11050
- }, template: "<!-- \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\u2500\u2500 -->\r\n<button\r\n #triggerRef\r\n type=\"button\"\r\n [id]=\"triggerId\"\r\n class=\"fly-select__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() || null\"\r\n [class.fly-select__trigger--placeholder]=\"!hasSelection()\"\r\n [disabled]=\"effectiveDisabled()\"\r\n (click)=\"toggleOpen()\"\r\n (keydown)=\"onTriggerKeydown($event)\"\r\n>\r\n <span class=\"fly-select__trigger-label\">{{ triggerLabel() }}</span>\r\n\r\n @if (clearable() && hasSelection() && !effectiveDisabled()) {\r\n <span\r\n class=\"fly-select__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-select__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\u2500\u2500 -->\r\n<!-- Portaled to <body> by the component (see class docblock) \u2014 position: fixed, computed from\r\n the trigger. [style.*] bindings null out the unused axis (top XOR bottom, left XOR right)\r\n rather than leaving a stale 0, which is what lets the flip-above / RTL cases each win\r\n outright instead of fighting a leftover declaration from the other branch. -->\r\n@if (isOpen()) {\r\n <div\r\n #panelRef\r\n class=\"fly-select__panel\"\r\n [class.fly-select__panel--flip]=\"panelPosition()?.flipAbove\"\r\n [style.top.px]=\"panelPosition()?.top\"\r\n [style.bottom.px]=\"panelPosition()?.bottom\"\r\n [style.left.px]=\"panelPosition()?.left\"\r\n [style.right.px]=\"panelPosition()?.right\"\r\n [style.min-width.px]=\"panelPosition()?.minWidth\"\r\n [style.max-height.px]=\"panelPosition()?.maxHeight\"\r\n tabindex=\"-1\"\r\n (keydown)=\"onPanelKeydown($event)\"\r\n >\r\n @if (searchable()) {\r\n <div class=\"fly-select__search\">\r\n <input\r\n #searchRef\r\n type=\"text\"\r\n class=\"fly-select__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\r\n @if (multiple() && showSelectAll() && navigableOptions().length > 0) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-select__select-all\"\r\n (click)=\"toggleSelectAll()\"\r\n >\r\n {{ allVisibleSelected() ? deselectAllText() : selectAllText() }}\r\n </button>\r\n }\r\n\r\n <div\r\n class=\"fly-select__listbox\"\r\n role=\"listbox\"\r\n [id]=\"listboxId\"\r\n [attr.tabindex]=\"searchable() ? null : 0\"\r\n [attr.aria-multiselectable]=\"multiple() ? true : null\"\r\n [attr.aria-activedescendant]=\"activeDescendant() || null\"\r\n [attr.aria-label]=\"ariaLabel() || null\"\r\n >\r\n @for (group of renderGroups(); track group.key) {\r\n @if (group.key !== null) {\r\n <div class=\"fly-select__group-label\" role=\"presentation\">{{ group.key }}</div>\r\n }\r\n @for (opt of group.options; track opt.value) {\r\n <div\r\n class=\"fly-select__option\"\r\n role=\"option\"\r\n tabindex=\"-1\"\r\n [id]=\"opt.disabled ? null : optionId(navIndexOf(opt.value))\"\r\n [attr.aria-selected]=\"isSelected(opt.value)\"\r\n [attr.aria-disabled]=\"opt.disabled ? true : null\"\r\n [class.fly-select__option--selected]=\"isSelected(opt.value)\"\r\n [class.fly-select__option--active]=\"!opt.disabled && navIndexOf(opt.value) === activeNavIndex()\"\r\n [class.fly-select__option--disabled]=\"opt.disabled\"\r\n (click)=\"onOptionClick(opt)\"\r\n (keydown.enter)=\"onOptionClick(opt)\"\r\n (mouseenter)=\"onOptionHover(navIndexOf(opt.value))\"\r\n >\r\n @if (multiple()) {\r\n <span class=\"fly-select__checkbox\" aria-hidden=\"true\">\r\n @if (isSelected(opt.value)) { <span class=\"fly-select__checkbox-tick\">\u2713</span> }\r\n </span>\r\n } @else {\r\n <span class=\"fly-select__check\" aria-hidden=\"true\">\r\n @if (isSelected(opt.value)) { \u2713 }\r\n </span>\r\n }\r\n <span class=\"fly-select__option-label\">{{ opt.label }}</span>\r\n </div>\r\n }\r\n }\r\n\r\n @if (filteredOptions().length === 0) {\r\n <div class=\"fly-select__empty\" role=\"presentation\">{{ noResultsText() }}</div>\r\n }\r\n </div>\r\n </div>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after,.fly-select__panel,.fly-select__panel *,.fly-select__panel *:before,.fly-select__panel *:after{box-sizing:border-box}:host{display:inline-block;inline-size:100%;min-inline-size:12rem}.fly-select__trigger{display:flex;align-items:center;gap:8px;inline-size:100%;block-size:36px;padding-inline:11px;border:1px solid var(--w14);border-radius:10px;background:var(--w05);color:var(--w85);font:500 13px/1.3 var(--font-family);text-align:start;cursor:pointer;transition:border-color var(--nova-duration-hover) var(--nova-ease-micro),background var(--nova-duration-hover) var(--nova-ease-micro)}.fly-select__trigger:hover:not(:disabled){background:var(--w07)}.fly-select__trigger:focus-visible{outline:3px solid var(--accent);outline-offset:3px;border-radius:inherit}.fly-select__trigger:disabled{opacity:.38;filter:saturate(.6);cursor:not-allowed}:host(.fly-select--open) .fly-select__trigger{border-color:var(--accent);background:var(--w08)}.fly-select__trigger--placeholder .fly-select__trigger-label{color:var(--w42)}.fly-select__trigger-label{flex:1 1 auto;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.fly-select__clear{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;inline-size:18px;block-size:18px;border-radius:50%;color:var(--w42);font-size:11px;line-height:1;cursor:pointer;transition:background var(--nova-duration-hover) var(--nova-ease-micro),color var(--nova-duration-hover) var(--nova-ease-micro)}.fly-select__clear:hover{background:var(--w1);color:var(--w85)}.fly-select__chevron{flex:0 0 auto;color:var(--w45);font-size:10px;transition:transform .22s var(--nova-ease-structural)}:host(.fly-select--open) .fly-select__chevron{transform:rotate(180deg)}@media(pointer:coarse){.fly-select__trigger{min-block-size:44px}.fly-select__clear{inline-size:24px;block-size:24px}}.fly-select__panel{position:fixed;z-index:1000;display:flex;flex-direction:column;inline-size:max-content;max-inline-size:min(420px,90vw);overflow:hidden;padding:6px;border-radius:14px;animation:menuIn .24s var(--nova-ease-structural) both;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-a),var(--mat-menu-b));box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset);-webkit-backdrop-filter:var(--glass2-blur);backdrop-filter:var(--glass2-blur)}@media(prefers-reduced-transparency:reduce){.fly-select__panel{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.fly-select__panel:before,.fly-select__panel:after{display:none}}@media(prefers-contrast:more){.fly-select__panel{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.fly-select__panel:after{animation:none}}.fly-select__search{padding:6px;border-block-end:1px solid var(--w08)}.fly-select__search-input{inline-size:100%;block-size:30px;padding-inline:9px;border:1px solid var(--w14);border-radius:8px;background:var(--w05);color:var(--w85);font:500 13px/1.3 var(--font-family)}.fly-select__search-input::placeholder{color:var(--w42)}.fly-select__search-input:focus-visible{outline:none;border-color:var(--accent);background:var(--w07)}.fly-select__select-all{display:block;inline-size:100%;padding:7px 9px;border:0;border-block-end:1px solid var(--w08);background:transparent;color:var(--accent);font:600 12.5px/1.3 var(--font-family);text-align:start;cursor:pointer}.fly-select__select-all:hover{background:var(--w1)}.fly-select__listbox{flex:1 1 auto;min-block-size:0;overflow-y:auto;padding-block:4px}.fly-select__listbox:focus-visible{outline:none}.fly-select__group-label{padding:6px 9px 2px;color:var(--w42);font:var(--nova-font-eyebrow);letter-spacing:var(--nova-tracking-eyebrow);text-transform:uppercase}.fly-select__option{display:flex;align-items:center;gap:8px;min-block-size:32px;padding:7px 9px;border-radius:8px;color:var(--w85);font:500 13px/1.3 var(--font-family);cursor:pointer;transition:background var(--nova-duration-hover) var(--nova-ease-micro)}.fly-select__option--active{background:var(--w1)}.fly-select__option--selected{background:var(--w08);font-weight:600}.fly-select__option--selected.fly-select__option--active{background:var(--w08);box-shadow:inset 0 0 0 1px var(--accent)}.fly-select__option--disabled{opacity:.38;filter:saturate(.6);cursor:not-allowed;pointer-events:none}.fly-select__option-label{flex:1 1 auto;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.fly-select__check{flex:0 0 auto;inline-size:16px;color:var(--accent);font-size:14px;font-weight:600;text-align:center}.fly-select__checkbox{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;inline-size:16px;block-size:16px;border:1.5px solid var(--w22);border-radius:4px;transition:background var(--nova-duration-hover) var(--nova-ease-micro),border-color var(--nova-duration-hover) var(--nova-ease-micro)}.fly-select__option--selected .fly-select__checkbox{background:var(--accent);border-color:var(--accent)}.fly-select__checkbox-tick{color:var(--on-accent);font-size:10px;line-height:1}.fly-select__empty{padding:16px 9px;text-align:center;color:var(--w42);font:400 12.5px/1.4 var(--font-family)}@media(pointer:coarse){.fly-select__option{min-block-size:44px}}\n"] }]
11177
+ }, template: "<!-- \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\u2500\u2500 -->\r\n<button\r\n #triggerRef\r\n type=\"button\"\r\n [id]=\"triggerId\"\r\n class=\"fly-select__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() || null\"\r\n [class.fly-select__trigger--placeholder]=\"!hasSelection()\"\r\n [disabled]=\"effectiveDisabled()\"\r\n (click)=\"toggleOpen()\"\r\n (keydown)=\"onTriggerKeydown($event)\"\r\n>\r\n <span class=\"fly-select__trigger-label\">{{ triggerLabel() }}</span>\r\n\r\n @if (clearable() && hasSelection() && !effectiveDisabled()) {\r\n <span\r\n class=\"fly-select__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-select__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\u2500\u2500 -->\r\n<!-- Portaled to <body> by the component (see class docblock) \u2014 position: fixed, computed from\r\n the trigger. [style.*] bindings null out the unused axis (top XOR bottom, left XOR right)\r\n rather than leaving a stale 0, which is what lets the flip-above / RTL cases each win\r\n outright instead of fighting a leftover declaration from the other branch. -->\r\n@if (isOpen()) {\r\n <div\r\n #panelRef\r\n class=\"fly-select__panel\"\r\n [class.fly-select__panel--flip]=\"panelPosition()?.flipAbove\"\r\n [style.top.px]=\"panelPosition()?.top\"\r\n [style.bottom.px]=\"panelPosition()?.bottom\"\r\n [style.left.px]=\"panelPosition()?.left\"\r\n [style.right.px]=\"panelPosition()?.right\"\r\n [style.min-width.px]=\"panelPosition()?.minWidth\"\r\n [style.max-height.px]=\"panelPosition()?.maxHeight\"\r\n tabindex=\"-1\"\r\n (keydown)=\"onPanelKeydown($event)\"\r\n >\r\n @if (searchable()) {\r\n <div class=\"fly-select__search\">\r\n <input\r\n #searchRef\r\n type=\"text\"\r\n class=\"fly-select__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\r\n @if (multiple() && showSelectAll() && navigableOptions().length > 0) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-select__select-all\"\r\n (click)=\"toggleSelectAll()\"\r\n >\r\n {{ allVisibleSelected() ? deselectAllText() : selectAllText() }}\r\n </button>\r\n }\r\n\r\n <div\r\n class=\"fly-select__listbox\"\r\n role=\"listbox\"\r\n [id]=\"listboxId\"\r\n [attr.tabindex]=\"searchable() ? null : 0\"\r\n [attr.aria-multiselectable]=\"multiple() ? true : null\"\r\n [attr.aria-activedescendant]=\"activeDescendant() || null\"\r\n [attr.aria-label]=\"ariaLabel() || null\"\r\n >\r\n @for (group of renderGroups(); track group.key) {\r\n @if (group.key !== null) {\r\n <div class=\"fly-select__group-label\" role=\"presentation\">{{ group.key }}</div>\r\n }\r\n @for (opt of group.options; track opt.value) {\r\n <div\r\n class=\"fly-select__option\"\r\n role=\"option\"\r\n tabindex=\"-1\"\r\n [id]=\"opt.disabled ? null : optionId(navIndexOf(opt.value))\"\r\n [attr.aria-selected]=\"isSelected(opt.value)\"\r\n [attr.aria-disabled]=\"opt.disabled ? true : null\"\r\n [class.fly-select__option--selected]=\"isSelected(opt.value)\"\r\n [class.fly-select__option--active]=\"!opt.disabled && navIndexOf(opt.value) === activeNavIndex()\"\r\n [class.fly-select__option--disabled]=\"opt.disabled\"\r\n (click)=\"onOptionClick(opt)\"\r\n (keydown.enter)=\"onOptionClick(opt)\"\r\n (mouseenter)=\"onOptionHover(navIndexOf(opt.value))\"\r\n >\r\n @if (multiple()) {\r\n <span class=\"fly-select__checkbox\" aria-hidden=\"true\">\r\n @if (isSelected(opt.value)) { <span class=\"fly-select__checkbox-tick\">\u2713</span> }\r\n </span>\r\n } @else {\r\n <span class=\"fly-select__check\" aria-hidden=\"true\">\r\n @if (isSelected(opt.value)) { \u2713 }\r\n </span>\r\n }\r\n <span class=\"fly-select__option-label\">{{ opt.label }}</span>\r\n </div>\r\n }\r\n }\r\n\r\n @if (filteredOptions().length === 0) {\r\n <div class=\"fly-select__empty\" role=\"presentation\">{{ noResultsText() }}</div>\r\n }\r\n </div>\r\n </div>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after,.fly-select__panel,.fly-select__panel *,.fly-select__panel *:before,.fly-select__panel *:after{box-sizing:border-box}:host{display:inline-block;inline-size:100%;min-inline-size:12rem}.fly-select__trigger{display:flex;align-items:center;gap:8px;inline-size:100%;block-size:36px;padding-inline:11px;border:1px solid var(--w14);border-radius:10px;background:var(--w05);color:var(--w85);font:500 13px/1.3 var(--font-family);text-align:start;cursor:pointer;transition:border-color var(--nova-duration-hover) var(--nova-ease-micro),background var(--nova-duration-hover) var(--nova-ease-micro)}.fly-select__trigger:hover:not(:disabled){background:var(--w07)}.fly-select__trigger:focus-visible{outline:3px solid var(--accent);outline-offset:3px;border-radius:inherit}.fly-select__trigger:disabled{opacity:.38;filter:saturate(.6);cursor:not-allowed}:host(.fly-select--open) .fly-select__trigger{border-color:var(--accent);background:var(--w08)}.fly-select__trigger--placeholder .fly-select__trigger-label{color:var(--w42)}.fly-select__trigger-label{flex:1 1 auto;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.fly-select__clear{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;inline-size:18px;block-size:18px;border-radius:50%;color:var(--w42);font-size:11px;line-height:1;cursor:pointer;transition:background var(--nova-duration-hover) var(--nova-ease-micro),color var(--nova-duration-hover) var(--nova-ease-micro)}.fly-select__clear:hover{background:var(--w1);color:var(--w85)}.fly-select__chevron{flex:0 0 auto;color:var(--w45);font-size:10px;transition:transform .22s var(--nova-ease-structural)}:host(.fly-select--open) .fly-select__chevron{transform:rotate(180deg)}@media(pointer:coarse){.fly-select__trigger{min-block-size:44px}.fly-select__clear{inline-size:24px;block-size:24px}}.fly-select__panel{position:fixed;z-index:1000;display:flex;flex-direction:column;inline-size:max-content;max-inline-size:min(420px,90vw);overflow:hidden;padding:6px;border-radius:14px;animation:menuIn .24s var(--nova-ease-structural) both;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-a),var(--mat-menu-b));box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset);-webkit-backdrop-filter:var(--glass2-blur);backdrop-filter:var(--glass2-blur)}@media(prefers-reduced-transparency:reduce){.fly-select__panel{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.fly-select__panel:before,.fly-select__panel:after{display:none}}@media(prefers-contrast:more){.fly-select__panel{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.fly-select__panel:after{animation:none}}.fly-select__search{padding:6px;border-block-end:1px solid var(--w08)}.fly-select__search-input{inline-size:100%;block-size:30px;padding-inline:9px;border:1px solid var(--w14);border-radius:8px;background:var(--w05);color:var(--w85);font:500 13px/1.3 var(--font-family)}.fly-select__search-input::placeholder{color:var(--w42)}.fly-select__search-input:focus-visible{outline:none;border-color:var(--accent);background:var(--w07)}.fly-select__select-all{display:block;inline-size:100%;padding:7px 9px;border:0;border-block-end:1px solid var(--w08);background:transparent;color:var(--accent);font:600 12.5px/1.3 var(--font-family);text-align:start;cursor:pointer}.fly-select__select-all:hover{background:var(--w1)}.fly-select__listbox{flex:1 1 auto;min-block-size:0;overflow-y:auto;padding-block:4px}.fly-select__listbox:focus-visible{outline:none}.fly-select__group-label{padding:6px 9px 2px;color:var(--w5);font:var(--nova-font-eyebrow);letter-spacing:var(--nova-tracking-eyebrow);text-transform:uppercase}.fly-select__option{display:flex;align-items:center;gap:8px;min-block-size:32px;padding:7px 9px;border-radius:8px;color:var(--w85);font:500 13px/1.3 var(--font-family);cursor:pointer;transition:background var(--nova-duration-hover) var(--nova-ease-micro)}.fly-select__option--active{background:var(--w1)}.fly-select__option--selected{background:var(--w08);font-weight:600}.fly-select__option--selected.fly-select__option--active{background:var(--w08);box-shadow:inset 0 0 0 1px var(--accent)}.fly-select__option--disabled{opacity:.38;filter:saturate(.6);cursor:not-allowed;pointer-events:none}.fly-select__option-label{flex:1 1 auto;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.fly-select__check{flex:0 0 auto;inline-size:16px;color:var(--accent);font-size:14px;font-weight:600;text-align:center}.fly-select__checkbox{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;inline-size:16px;block-size:16px;border:1.5px solid var(--w22);border-radius:4px;transition:background var(--nova-duration-hover) var(--nova-ease-micro),border-color var(--nova-duration-hover) var(--nova-ease-micro)}.fly-select__option--selected .fly-select__checkbox{background:var(--accent);border-color:var(--accent)}.fly-select__checkbox-tick{color:var(--on-accent);font-size:10px;line-height:1}.fly-select__empty{padding:16px 9px;text-align:center;color:var(--w42);font:400 12.5px/1.4 var(--font-family)}@media(pointer:coarse){.fly-select__option{min-block-size:44px}}\n"] }]
11051
11178
  }], ctorParameters: () => [], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], showSelectAll: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSelectAll", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], noResultsLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "noResultsLabel", required: false }] }], selectAllLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectAllLabel", required: false }] }], deselectAllLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "deselectAllLabel", required: false }] }], clearLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearLabel", required: false }] }], summaryFormatter: [{ type: i0.Input, args: [{ isSignal: true, alias: "summaryFormatter", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], openedChange: [{ type: i0.Output, args: ["openedChange"] }], searchEl: [{
11052
11179
  type: ViewChild,
11053
11180
  args: ['searchRef']
@@ -16544,13 +16671,13 @@ class FlyMagicActionsComponent {
16544
16671
  this.isOverflowing.set(!!el && el.scrollWidth - el.clientWidth > 1);
16545
16672
  }
16546
16673
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyMagicActionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
16547
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyMagicActionsComponent, isStandalone: true, selector: "fly-magic-actions", inputs: { groups: { classPropertyName: "groups", publicName: "groups", isSignal: true, isRequired: true, transformFunction: null }, primary: { classPropertyName: "primary", publicName: "primary", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.dir": "i18n.direction()" } }, viewQueries: [{ propertyName: "rowRef", first: true, predicate: ["row"], descendants: true, isSignal: true }], ngImport: i0, template: "<!--\r\n Icon-only buttons carry the accessible name via [attr.aria-label] \u2014 the SVG\r\n glyph itself is aria-hidden (decorative, skill \u00A7accessibility rule 5). `kind:\r\n 'text'` actions rely on their own visible text instead. Disabled rows stay\r\n `aria-disabled` (never native `disabled`) so they remain focusable/hoverable \u2014\r\n a disabled action's whole point is to explain ITSELF via the auto-suffixed\r\n tooltip, which native `disabled` would suppress.\r\n\r\n The glyph is composed from ALLOWLISTED nodes bound as real attributes, never\r\n from `[innerHTML]`: `iconPath` is publisher-supplied, and an `<svg>` fragment\r\n is not a closed world (`<foreignObject>` is an HTML integration point, `<a\r\n xlink:href=\"javascript:\u2026\">` is live under the very click this button invites).\r\n `magic-actions-icon.ts` carries the full rationale; the shape below is its\r\n consequence \u2014 an attribute binding cannot introduce an element or a handler,\r\n so there is nothing left to sanitize.\r\n\r\n That block is authored ONCE, as `#actionButton`, and stamped by every place a\r\n contributed action is painted (group items and `primary`). It used to be\r\n inline in the group loop, which was fine while this component rendered groups\r\n only; with `primary` here too, a second copy would mean the next edit to the\r\n SECURITY-CRITICAL binding shape has to be made twice and can be made once.\r\n-->\r\n<ng-template #actionButton let-projected>\r\n <!--\r\n `let-projected` is `any` \u2014 Angular infers nothing for an `ngTemplateOutlet`\r\n context, and there is no built-in way to declare one. `asAction` is an\r\n identity function whose only job is to give this block a TYPED local, so\r\n every read below (`.kind`, `.disabled`, `.menu`) is checked by the template\r\n compiler instead of silently resolving to `undefined` on a typo. Without it\r\n the one place a `MagicBarActionView` is actually painted would be the one\r\n place its shape is not verified.\r\n -->\r\n @let action = asAction(projected);\r\n @if (action.kind === 'text') {\r\n <button\r\n type=\"button\"\r\n class=\"fma-btn fma-btn--text\"\r\n [attr.data-tone]=\"toneAttr(action)\"\r\n [attr.aria-disabled]=\"action.disabled ? 'true' : null\"\r\n [attr.aria-pressed]=\"pressedAttr(action)\"\r\n [flyTooltip]=\"tooltipFor(action)\"\r\n [flyTooltipDelay]=\"450\"\r\n (click)=\"activate(action, $event)\"\r\n >{{ resolvedLabel(action) }}</button>\r\n } @else {\r\n <button\r\n type=\"button\"\r\n class=\"fma-btn\"\r\n [class.fma-btn--badge]=\"hasBadge(action)\"\r\n [attr.data-tone]=\"toneAttr(action)\"\r\n [attr.aria-disabled]=\"action.disabled ? 'true' : null\"\r\n [attr.aria-pressed]=\"pressedAttr(action)\"\r\n [attr.aria-haspopup]=\"action.menu ? 'menu' : null\"\r\n [attr.aria-expanded]=\"action.menu ? (openMenuActionId() === action.id ? 'true' : 'false') : null\"\r\n [attr.aria-label]=\"action.menu ? triggerAriaLabel(action) : resolvedLabel(action)\"\r\n [flyTooltip]=\"tooltipFor(action)\"\r\n [flyTooltipDelay]=\"450\"\r\n (click)=\"activate(action, $event)\"\r\n >\r\n <svg\r\n class=\"fma-btn__icon\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"1.8\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n aria-hidden=\"true\"\r\n >\r\n @for (node of iconNodes(action); track $index) {\r\n @switch (node.tag) {\r\n @case ('path') {\r\n <svg:path\r\n [attr.d]=\"node.attrs['d']\"\r\n [attr.fill-rule]=\"node.attrs['fill-rule']\"\r\n [attr.clip-rule]=\"node.attrs['clip-rule']\"\r\n />\r\n }\r\n @case ('circle') {\r\n <svg:circle\r\n [attr.cx]=\"node.attrs['cx']\"\r\n [attr.cy]=\"node.attrs['cy']\"\r\n [attr.r]=\"node.attrs['r']\"\r\n />\r\n }\r\n @case ('rect') {\r\n <svg:rect\r\n [attr.x]=\"node.attrs['x']\"\r\n [attr.y]=\"node.attrs['y']\"\r\n [attr.width]=\"node.attrs['width']\"\r\n [attr.height]=\"node.attrs['height']\"\r\n [attr.rx]=\"node.attrs['rx']\"\r\n [attr.ry]=\"node.attrs['ry']\"\r\n />\r\n }\r\n @case ('ellipse') {\r\n <svg:ellipse\r\n [attr.cx]=\"node.attrs['cx']\"\r\n [attr.cy]=\"node.attrs['cy']\"\r\n [attr.rx]=\"node.attrs['rx']\"\r\n [attr.ry]=\"node.attrs['ry']\"\r\n />\r\n }\r\n @case ('line') {\r\n <svg:line\r\n [attr.x1]=\"node.attrs['x1']\"\r\n [attr.y1]=\"node.attrs['y1']\"\r\n [attr.x2]=\"node.attrs['x2']\"\r\n [attr.y2]=\"node.attrs['y2']\"\r\n />\r\n }\r\n @case ('polyline') {\r\n <svg:polyline [attr.points]=\"node.attrs['points']\" />\r\n }\r\n @case ('polygon') {\r\n <svg:polygon [attr.points]=\"node.attrs['points']\" />\r\n }\r\n }\r\n }\r\n </svg>\r\n @if (hasBadge(action)) {\r\n <span class=\"fma-btn__badge\" aria-hidden=\"true\"></span>\r\n }\r\n </button>\r\n }\r\n</ng-template>\r\n\r\n<!--\r\n `.fma` is the SCROLLING row and holds the contributed groups only. The accent\r\n CTA is a SIBLING of it, outside the fade mask and outside the scroll container,\r\n because it is the row's anchor \u2014 a call to action that dissolves into a\r\n gradient, or that you have to scroll sideways to reach, inverts the affordance.\r\n It is also what makes `isOverflowing` measurable at all: see the component.\r\n-->\r\n@if (groups().length) {\r\n <div #row class=\"fma\" [class.fma--overflow]=\"isOverflowing()\">\r\n @for (group of groups(); track group.id) {\r\n <div\r\n class=\"fma-group\"\r\n [class.fma-group--framed]=\"group.framed\"\r\n role=\"group\"\r\n [attr.aria-label]=\"i18n.t(group.labelKey)\"\r\n >\r\n @for (action of group.items; track action.id) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"actionButton\"\r\n [ngTemplateOutletContext]=\"{ $implicit: action }\"\r\n />\r\n }\r\n </div>\r\n @if (group.framed) {\r\n <span class=\"fma-sep\" aria-hidden=\"true\"></span>\r\n }\r\n }\r\n </div>\r\n}\r\n\r\n<!--\r\n The accent CTA. The disc is a WRAPPER, not a modifier on the button, and that\r\n is load-bearing rather than cosmetic: `.fma-btn`'s hover/press feedback is an\r\n `animation` (`iconWiggle` / `iconBounce`), and an animation that touches\r\n `transform` beats a declared `transform` \u2014 so a hover lift authored on the\r\n button itself would simply never render. The wrapper owns the disc, the lift\r\n and the shadow; the button stays transparent and keeps the DS's own toolbar\r\n feedback. It re-points `--chrome-ink` rather than styling `.fma-btn` through a\r\n descendant selector so the glyph colour inherits the way custom properties do.\r\n-->\r\n@if (primary(); as p) {\r\n <span class=\"fma-primary\" [class.fma-primary--disabled]=\"p.disabled\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"actionButton\"\r\n [ngTemplateOutletContext]=\"{ $implicit: p }\"\r\n />\r\n </span>\r\n}\r\n\r\n<!--\r\n ONE radio menu, hoisted out of the group loops (S1-review F9). Only one is open\r\n at a time (`openMenuActionId`), so a single instance is enough \u2014 and rendering\r\n it beside its button would insert a non-`.fma-btn` element into the group,\r\n silently shifting every later button's `:nth-child` index and with it the\r\n `toolbarPopIn` stagger delay the `.fma-btn` rule assigns. Keeping every\r\n staggered parent's children homogeneous is what makes that rule's comment true.\r\n It portals itself to <body>, so its position is unaffected by where it is\r\n declared.\r\n-->\r\n@if (openAction(); as open) {\r\n <fly-context-menu\r\n [anchor]=\"menuAnchorEl()\"\r\n [sections]=\"menuSections(open)\"\r\n (action)=\"onMenuAction(open, $event)\"\r\n (closed)=\"closeMenu()\"\r\n />\r\n}\r\n", styles: [":host{display:flex;align-items:center;gap:8px;min-inline-size:0}.fma{display:flex;align-items:center;gap:8px;flex:0 1 auto;min-inline-size:0;overflow:hidden;flex-wrap:nowrap}.fma--overflow{overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none;-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent 100%);mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent 100%)}.fma--overflow::-webkit-scrollbar{display:none}:host([dir=rtl]) .fma--overflow{-webkit-mask-image:linear-gradient(to left,#000 calc(100% - 28px),transparent 100%);mask-image:linear-gradient(to left,#000 calc(100% - 28px),transparent 100%)}.fma-group{display:flex;align-items:center;gap:8px;flex:none}.fma-group--framed{gap:2px}.fma-sep{flex:0 0 auto;inline-size:1px;block-size:18px;background:var(--w14)}.fma-btn{position:relative;flex:0 0 auto;inline-size:30px;block-size:30px;display:grid;place-items:center;border:0;border-radius:50%;background:transparent;color:var(--chrome-ink);cursor:pointer;padding:0;animation:toolbarPopIn .3s var(--nova-ease-structural) both}.fma-btn:nth-child(1){animation-delay:0ms}.fma-btn:nth-child(2){animation-delay:40ms}.fma-btn:nth-child(3){animation-delay:80ms}.fma-btn:nth-child(4){animation-delay:.12s}.fma-btn:nth-child(5){animation-delay:.16s}.fma-btn:nth-child(6){animation-delay:.2s}.fma-btn:nth-child(7){animation-delay:.24s}.fma-btn:nth-child(8){animation-delay:.28s}.fma-btn:nth-child(9){animation-delay:.32s}.fma-btn:nth-child(10){animation-delay:.36s}.fma-btn:nth-child(11){animation-delay:.4s}.fma-btn:nth-child(12){animation-delay:.44s}.fma-btn:nth-child(n+13){animation-delay:.44s}.fma-btn:not([aria-disabled=true]):hover{background:var(--w1);color:var(--chrome-ink-hover);animation:iconWiggle .5s ease}.fma-btn:not([aria-disabled=true]):active{animation:iconBounce .38s var(--nova-ease-overshoot)}.fma-btn:focus-visible{outline:3px solid var(--accent);outline-offset:3px;border-radius:inherit}.fma-btn[data-tone=danger]{color:var(--sys-red)}.fma-btn[data-tone=danger]:not([aria-disabled=true]):hover{background:color-mix(in oklab,var(--sys-red) 18%,transparent);color:var(--sys-red)}.fma-btn[data-tone=success]{color:var(--sys-green)}.fma-btn[data-tone=success]:not([aria-disabled=true]):hover{background:color-mix(in oklab,var(--sys-green) 18%,transparent);color:var(--sys-green)}.fma-btn[aria-pressed=true]{background:var(--tint-sel);color:var(--chrome-ink-hover)}.fma-btn[aria-pressed=true][data-tone=success]{background:color-mix(in oklab,var(--sys-green) 22%,transparent);color:var(--sys-green)}.fma-btn[aria-disabled=true]{opacity:.38;filter:saturate(.6);animation:none;cursor:default}.fma-btn__icon{inline-size:16px;block-size:16px;pointer-events:none}.fma-btn__badge{position:absolute;inset-block-start:3px;inset-inline-end:3px;inline-size:7px;block-size:7px;border-radius:50%;background:var(--accent);pointer-events:none}.fma-primary{--chrome-ink: var(--on-accent);--chrome-ink-hover: var(--on-accent);flex:none;display:grid;place-items:center;inline-size:30px;block-size:30px;border-radius:50%;background:var(--accent);transition:box-shadow var(--nova-duration-hover) var(--nova-ease-micro),transform var(--nova-duration-hover) var(--nova-ease-micro)}.fma-primary:hover{box-shadow:0 8px 20px var(--tint-sel);transform:translateY(-1px)}.fma-primary--disabled{opacity:.38;filter:saturate(.6)}.fma-primary--disabled:hover{box-shadow:none;transform:none}.fma-btn--text{inline-size:auto;block-size:30px;border-radius:99px;padding-inline:12px;font-family:inherit;font-size:12.5px;font-weight:600;line-height:1;white-space:nowrap}@media(pointer:coarse){.fma-btn,.fma-primary{inline-size:44px;block-size:44px}}\n"], dependencies: [{ kind: "component", type: ContextMenuComponent, selector: "fly-context-menu", inputs: ["x", "y", "anchor", "align", "placement", "sections", "boundary"], outputs: ["action", "closed"] }, { kind: "directive", type: FlyTooltipDirective, selector: "[flyTooltip], [data-tooltip]", inputs: ["flyTooltip", "flyTooltipPlacement", "flyTooltipDisabled", "flyTooltipDelay"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
16674
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyMagicActionsComponent, isStandalone: true, selector: "fly-magic-actions", inputs: { groups: { classPropertyName: "groups", publicName: "groups", isSignal: true, isRequired: true, transformFunction: null }, primary: { classPropertyName: "primary", publicName: "primary", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.dir": "i18n.direction()" } }, viewQueries: [{ propertyName: "rowRef", first: true, predicate: ["row"], descendants: true, isSignal: true }], ngImport: i0, template: "<!--\r\n Icon-only buttons carry the accessible name via [attr.aria-label] \u2014 the SVG\r\n glyph itself is aria-hidden (decorative, skill \u00A7accessibility rule 5). `kind:\r\n 'text'` actions rely on their own visible text instead. Disabled rows stay\r\n `aria-disabled` (never native `disabled`) so they remain focusable/hoverable \u2014\r\n a disabled action's whole point is to explain ITSELF via the auto-suffixed\r\n tooltip, which native `disabled` would suppress.\r\n\r\n The glyph is composed from ALLOWLISTED nodes bound as real attributes, never\r\n from `[innerHTML]`: `iconPath` is publisher-supplied, and an `<svg>` fragment\r\n is not a closed world (`<foreignObject>` is an HTML integration point, `<a\r\n xlink:href=\"javascript:\u2026\">` is live under the very click this button invites).\r\n `magic-actions-icon.ts` carries the full rationale; the shape below is its\r\n consequence \u2014 an attribute binding cannot introduce an element or a handler,\r\n so there is nothing left to sanitize.\r\n\r\n That block is authored ONCE, as `#actionButton`, and stamped by every place a\r\n contributed action is painted (group items and `primary`). It used to be\r\n inline in the group loop, which was fine while this component rendered groups\r\n only; with `primary` here too, a second copy would mean the next edit to the\r\n SECURITY-CRITICAL binding shape has to be made twice and can be made once.\r\n-->\r\n<ng-template #actionButton let-projected>\r\n <!--\r\n `let-projected` is `any` \u2014 Angular infers nothing for an `ngTemplateOutlet`\r\n context, and there is no built-in way to declare one. `asAction` is an\r\n identity function whose only job is to give this block a TYPED local, so\r\n every read below (`.kind`, `.disabled`, `.menu`) is checked by the template\r\n compiler instead of silently resolving to `undefined` on a typo. Without it\r\n the one place a `MagicBarActionView` is actually painted would be the one\r\n place its shape is not verified.\r\n -->\r\n @let action = asAction(projected);\r\n @if (action.kind === 'text') {\r\n <button\r\n type=\"button\"\r\n class=\"fma-btn fma-btn--text\"\r\n [attr.data-tone]=\"toneAttr(action)\"\r\n [attr.aria-disabled]=\"action.disabled ? 'true' : null\"\r\n [attr.aria-pressed]=\"pressedAttr(action)\"\r\n [flyTooltip]=\"tooltipFor(action)\"\r\n [flyTooltipDelay]=\"450\"\r\n (click)=\"activate(action, $event)\"\r\n >{{ resolvedLabel(action) }}</button>\r\n } @else {\r\n <button\r\n type=\"button\"\r\n class=\"fma-btn\"\r\n [class.fma-btn--badge]=\"hasBadge(action)\"\r\n [attr.data-tone]=\"toneAttr(action)\"\r\n [attr.aria-disabled]=\"action.disabled ? 'true' : null\"\r\n [attr.aria-pressed]=\"pressedAttr(action)\"\r\n [attr.aria-haspopup]=\"action.menu ? 'menu' : null\"\r\n [attr.aria-expanded]=\"action.menu ? (openMenuActionId() === action.id ? 'true' : 'false') : null\"\r\n [attr.aria-label]=\"action.menu ? triggerAriaLabel(action) : resolvedLabel(action)\"\r\n [flyTooltip]=\"tooltipFor(action)\"\r\n [flyTooltipDelay]=\"450\"\r\n (click)=\"activate(action, $event)\"\r\n >\r\n <svg\r\n class=\"fma-btn__icon\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"1.8\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n aria-hidden=\"true\"\r\n >\r\n @for (node of iconNodes(action); track $index) {\r\n @switch (node.tag) {\r\n @case ('path') {\r\n <svg:path\r\n [attr.d]=\"node.attrs['d']\"\r\n [attr.fill-rule]=\"node.attrs['fill-rule']\"\r\n [attr.clip-rule]=\"node.attrs['clip-rule']\"\r\n />\r\n }\r\n @case ('circle') {\r\n <svg:circle\r\n [attr.cx]=\"node.attrs['cx']\"\r\n [attr.cy]=\"node.attrs['cy']\"\r\n [attr.r]=\"node.attrs['r']\"\r\n />\r\n }\r\n @case ('rect') {\r\n <svg:rect\r\n [attr.x]=\"node.attrs['x']\"\r\n [attr.y]=\"node.attrs['y']\"\r\n [attr.width]=\"node.attrs['width']\"\r\n [attr.height]=\"node.attrs['height']\"\r\n [attr.rx]=\"node.attrs['rx']\"\r\n [attr.ry]=\"node.attrs['ry']\"\r\n />\r\n }\r\n @case ('ellipse') {\r\n <svg:ellipse\r\n [attr.cx]=\"node.attrs['cx']\"\r\n [attr.cy]=\"node.attrs['cy']\"\r\n [attr.rx]=\"node.attrs['rx']\"\r\n [attr.ry]=\"node.attrs['ry']\"\r\n />\r\n }\r\n @case ('line') {\r\n <svg:line\r\n [attr.x1]=\"node.attrs['x1']\"\r\n [attr.y1]=\"node.attrs['y1']\"\r\n [attr.x2]=\"node.attrs['x2']\"\r\n [attr.y2]=\"node.attrs['y2']\"\r\n />\r\n }\r\n @case ('polyline') {\r\n <svg:polyline [attr.points]=\"node.attrs['points']\" />\r\n }\r\n @case ('polygon') {\r\n <svg:polygon [attr.points]=\"node.attrs['points']\" />\r\n }\r\n }\r\n }\r\n </svg>\r\n @if (hasBadge(action)) {\r\n <span class=\"fma-btn__badge\" aria-hidden=\"true\"></span>\r\n }\r\n </button>\r\n }\r\n</ng-template>\r\n\r\n<!--\r\n `.fma` is the SCROLLING row and holds the contributed groups only. The accent\r\n CTA is a SIBLING of it, outside the fade mask and outside the scroll container,\r\n because it is the row's anchor \u2014 a call to action that dissolves into a\r\n gradient, or that you have to scroll sideways to reach, inverts the affordance.\r\n It is also what makes `isOverflowing` measurable at all: see the component.\r\n-->\r\n@if (groups().length) {\r\n <div #row class=\"fma\" [class.fma--overflow]=\"isOverflowing()\">\r\n @for (group of groups(); track group.id) {\r\n <div\r\n class=\"fma-group\"\r\n [class.fma-group--framed]=\"group.framed\"\r\n role=\"group\"\r\n [attr.aria-label]=\"i18n.t(group.labelKey)\"\r\n >\r\n @for (action of group.items; track action.id) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"actionButton\"\r\n [ngTemplateOutletContext]=\"{ $implicit: action }\"\r\n />\r\n }\r\n </div>\r\n @if (group.framed) {\r\n <span class=\"fma-sep\" aria-hidden=\"true\"></span>\r\n }\r\n }\r\n </div>\r\n}\r\n\r\n<!--\r\n The accent CTA. The disc is a WRAPPER, not a modifier on the button, and that\r\n is load-bearing rather than cosmetic: `.fma-btn`'s hover/press feedback is an\r\n `animation` (`iconWiggle` / `iconBounce`), and an animation that touches\r\n `transform` beats a declared `transform` \u2014 so a hover lift authored on the\r\n button itself would simply never render. The wrapper owns the disc, the lift\r\n and the shadow; the button stays transparent and keeps the DS's own toolbar\r\n feedback. It re-points `--chrome-ink` rather than styling `.fma-btn` through a\r\n descendant selector so the glyph colour inherits the way custom properties do.\r\n-->\r\n@if (primary(); as p) {\r\n <span class=\"fma-primary\" [class.fma-primary--disabled]=\"p.disabled\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"actionButton\"\r\n [ngTemplateOutletContext]=\"{ $implicit: p }\"\r\n />\r\n </span>\r\n}\r\n\r\n<!--\r\n ONE radio menu, hoisted out of the group loops (S1-review F9). Only one is open\r\n at a time (`openMenuActionId`), so a single instance is enough \u2014 and rendering\r\n it beside its button would insert a non-`.fma-btn` element into the group,\r\n silently shifting every later button's `:nth-child` index and with it the\r\n `toolbarPopIn` stagger delay the `.fma-btn` rule assigns. Keeping every\r\n staggered parent's children homogeneous is what makes that rule's comment true.\r\n It portals itself to <body>, so its position is unaffected by where it is\r\n declared.\r\n-->\r\n@if (openAction(); as open) {\r\n <fly-context-menu\r\n [anchor]=\"menuAnchorEl()\"\r\n [sections]=\"menuSections(open)\"\r\n (action)=\"onMenuAction(open, $event)\"\r\n (closed)=\"closeMenu()\"\r\n />\r\n}\r\n", styles: [":host{display:flex;align-items:center;gap:8px;min-inline-size:0}.fma{display:flex;align-items:center;gap:8px;flex:0 1 auto;min-inline-size:0;overflow:hidden;flex-wrap:nowrap}.fma--overflow{overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none;-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent 100%);mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent 100%)}.fma--overflow::-webkit-scrollbar{display:none}:host([dir=rtl]) .fma--overflow{-webkit-mask-image:linear-gradient(to left,#000 calc(100% - 28px),transparent 100%);mask-image:linear-gradient(to left,#000 calc(100% - 28px),transparent 100%)}.fma-group{display:flex;align-items:center;gap:8px;flex:none}.fma-group--framed{gap:2px}.fma-sep{flex:0 0 auto;inline-size:1px;block-size:18px;background:var(--w14)}.fma-btn{position:relative;flex:0 0 auto;inline-size:30px;block-size:30px;display:grid;place-items:center;border:0;border-radius:50%;background:transparent;color:var(--chrome-ink);cursor:pointer;padding:0;animation:toolbarPopIn .3s var(--nova-ease-structural) both}.fma-btn:nth-child(1){animation-delay:0ms}.fma-btn:nth-child(2){animation-delay:40ms}.fma-btn:nth-child(3){animation-delay:80ms}.fma-btn:nth-child(4){animation-delay:.12s}.fma-btn:nth-child(5){animation-delay:.16s}.fma-btn:nth-child(6){animation-delay:.2s}.fma-btn:nth-child(7){animation-delay:.24s}.fma-btn:nth-child(8){animation-delay:.28s}.fma-btn:nth-child(9){animation-delay:.32s}.fma-btn:nth-child(10){animation-delay:.36s}.fma-btn:nth-child(11){animation-delay:.4s}.fma-btn:nth-child(12){animation-delay:.44s}.fma-btn:nth-child(n+13){animation-delay:.44s}.fma-btn:not([aria-disabled=true]):hover{background:var(--w1);color:var(--chrome-ink-hover);animation:iconWiggle .5s ease}.fma-btn:not([aria-disabled=true]):active{animation:iconBounce .38s var(--nova-ease-overshoot)}.fma-btn:focus-visible{outline:3px solid var(--accent);outline-offset:3px;border-radius:inherit}.fma-btn[data-tone=danger]{color:var(--sys-red)}.fma-btn[data-tone=danger]:not([aria-disabled=true]):hover{background:color-mix(in oklab,var(--sys-red) 18%,transparent);color:var(--sys-red)}.fma-btn[data-tone=success]{color:var(--sys-green)}.fma-btn[data-tone=success]:not([aria-disabled=true]):hover{background:color-mix(in oklab,var(--sys-green) 18%,transparent);color:var(--sys-green)}.fma-btn[aria-pressed=true]{background:var(--tint-sel);color:var(--chrome-ink-hover)}.fma-btn[aria-pressed=true][data-tone=success]{background:color-mix(in oklab,var(--sys-green) 22%,transparent);color:var(--sys-green)}.fma-btn[aria-disabled=true]{opacity:.38;filter:saturate(.6);animation:none;cursor:default}.fma-btn__icon{inline-size:16px;block-size:16px;pointer-events:none}.fma-btn__badge{position:absolute;inset-block-start:3px;inset-inline-end:3px;inline-size:7px;block-size:7px;border-radius:50%;background:var(--accent);pointer-events:none}.fma-primary{--chrome-ink: var(--on-accent);--chrome-ink-hover: var(--on-accent);flex:none;display:grid;place-items:center;inline-size:30px;block-size:30px;border-radius:50%;background:var(--accent);transition:box-shadow var(--nova-duration-hover) var(--nova-ease-micro),transform var(--nova-duration-hover) var(--nova-ease-micro)}.fma-primary:hover{box-shadow:0 8px 20px var(--tint-sel);transform:translateY(-1px)}.fma-primary--disabled{opacity:.38;filter:saturate(.6)}.fma-primary--disabled:hover{box-shadow:none;transform:none}.fma-btn--text{inline-size:auto;block-size:30px;border-radius:99px;padding-inline:12px;font-family:inherit;font-size:12.5px;font-weight:600;line-height:1;white-space:nowrap}@media(pointer:coarse){.fma-btn{inline-size:44px;block-size:44px}.fma-btn--text{inline-size:auto;min-inline-size:44px}.fma-primary{inline-size:44px;block-size:44px}}\n"], dependencies: [{ kind: "component", type: ContextMenuComponent, selector: "fly-context-menu", inputs: ["x", "y", "anchor", "align", "placement", "sections", "boundary"], outputs: ["action", "closed"] }, { kind: "directive", type: FlyTooltipDirective, selector: "[flyTooltip], [data-tooltip]", inputs: ["flyTooltip", "flyTooltipPlacement", "flyTooltipDisabled", "flyTooltipDelay"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
16548
16675
  }
16549
16676
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyMagicActionsComponent, decorators: [{
16550
16677
  type: Component,
16551
16678
  args: [{ selector: 'fly-magic-actions', standalone: true, imports: [ContextMenuComponent, FlyTooltipDirective, NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, host: {
16552
16679
  '[attr.dir]': 'i18n.direction()',
16553
- }, template: "<!--\r\n Icon-only buttons carry the accessible name via [attr.aria-label] \u2014 the SVG\r\n glyph itself is aria-hidden (decorative, skill \u00A7accessibility rule 5). `kind:\r\n 'text'` actions rely on their own visible text instead. Disabled rows stay\r\n `aria-disabled` (never native `disabled`) so they remain focusable/hoverable \u2014\r\n a disabled action's whole point is to explain ITSELF via the auto-suffixed\r\n tooltip, which native `disabled` would suppress.\r\n\r\n The glyph is composed from ALLOWLISTED nodes bound as real attributes, never\r\n from `[innerHTML]`: `iconPath` is publisher-supplied, and an `<svg>` fragment\r\n is not a closed world (`<foreignObject>` is an HTML integration point, `<a\r\n xlink:href=\"javascript:\u2026\">` is live under the very click this button invites).\r\n `magic-actions-icon.ts` carries the full rationale; the shape below is its\r\n consequence \u2014 an attribute binding cannot introduce an element or a handler,\r\n so there is nothing left to sanitize.\r\n\r\n That block is authored ONCE, as `#actionButton`, and stamped by every place a\r\n contributed action is painted (group items and `primary`). It used to be\r\n inline in the group loop, which was fine while this component rendered groups\r\n only; with `primary` here too, a second copy would mean the next edit to the\r\n SECURITY-CRITICAL binding shape has to be made twice and can be made once.\r\n-->\r\n<ng-template #actionButton let-projected>\r\n <!--\r\n `let-projected` is `any` \u2014 Angular infers nothing for an `ngTemplateOutlet`\r\n context, and there is no built-in way to declare one. `asAction` is an\r\n identity function whose only job is to give this block a TYPED local, so\r\n every read below (`.kind`, `.disabled`, `.menu`) is checked by the template\r\n compiler instead of silently resolving to `undefined` on a typo. Without it\r\n the one place a `MagicBarActionView` is actually painted would be the one\r\n place its shape is not verified.\r\n -->\r\n @let action = asAction(projected);\r\n @if (action.kind === 'text') {\r\n <button\r\n type=\"button\"\r\n class=\"fma-btn fma-btn--text\"\r\n [attr.data-tone]=\"toneAttr(action)\"\r\n [attr.aria-disabled]=\"action.disabled ? 'true' : null\"\r\n [attr.aria-pressed]=\"pressedAttr(action)\"\r\n [flyTooltip]=\"tooltipFor(action)\"\r\n [flyTooltipDelay]=\"450\"\r\n (click)=\"activate(action, $event)\"\r\n >{{ resolvedLabel(action) }}</button>\r\n } @else {\r\n <button\r\n type=\"button\"\r\n class=\"fma-btn\"\r\n [class.fma-btn--badge]=\"hasBadge(action)\"\r\n [attr.data-tone]=\"toneAttr(action)\"\r\n [attr.aria-disabled]=\"action.disabled ? 'true' : null\"\r\n [attr.aria-pressed]=\"pressedAttr(action)\"\r\n [attr.aria-haspopup]=\"action.menu ? 'menu' : null\"\r\n [attr.aria-expanded]=\"action.menu ? (openMenuActionId() === action.id ? 'true' : 'false') : null\"\r\n [attr.aria-label]=\"action.menu ? triggerAriaLabel(action) : resolvedLabel(action)\"\r\n [flyTooltip]=\"tooltipFor(action)\"\r\n [flyTooltipDelay]=\"450\"\r\n (click)=\"activate(action, $event)\"\r\n >\r\n <svg\r\n class=\"fma-btn__icon\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"1.8\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n aria-hidden=\"true\"\r\n >\r\n @for (node of iconNodes(action); track $index) {\r\n @switch (node.tag) {\r\n @case ('path') {\r\n <svg:path\r\n [attr.d]=\"node.attrs['d']\"\r\n [attr.fill-rule]=\"node.attrs['fill-rule']\"\r\n [attr.clip-rule]=\"node.attrs['clip-rule']\"\r\n />\r\n }\r\n @case ('circle') {\r\n <svg:circle\r\n [attr.cx]=\"node.attrs['cx']\"\r\n [attr.cy]=\"node.attrs['cy']\"\r\n [attr.r]=\"node.attrs['r']\"\r\n />\r\n }\r\n @case ('rect') {\r\n <svg:rect\r\n [attr.x]=\"node.attrs['x']\"\r\n [attr.y]=\"node.attrs['y']\"\r\n [attr.width]=\"node.attrs['width']\"\r\n [attr.height]=\"node.attrs['height']\"\r\n [attr.rx]=\"node.attrs['rx']\"\r\n [attr.ry]=\"node.attrs['ry']\"\r\n />\r\n }\r\n @case ('ellipse') {\r\n <svg:ellipse\r\n [attr.cx]=\"node.attrs['cx']\"\r\n [attr.cy]=\"node.attrs['cy']\"\r\n [attr.rx]=\"node.attrs['rx']\"\r\n [attr.ry]=\"node.attrs['ry']\"\r\n />\r\n }\r\n @case ('line') {\r\n <svg:line\r\n [attr.x1]=\"node.attrs['x1']\"\r\n [attr.y1]=\"node.attrs['y1']\"\r\n [attr.x2]=\"node.attrs['x2']\"\r\n [attr.y2]=\"node.attrs['y2']\"\r\n />\r\n }\r\n @case ('polyline') {\r\n <svg:polyline [attr.points]=\"node.attrs['points']\" />\r\n }\r\n @case ('polygon') {\r\n <svg:polygon [attr.points]=\"node.attrs['points']\" />\r\n }\r\n }\r\n }\r\n </svg>\r\n @if (hasBadge(action)) {\r\n <span class=\"fma-btn__badge\" aria-hidden=\"true\"></span>\r\n }\r\n </button>\r\n }\r\n</ng-template>\r\n\r\n<!--\r\n `.fma` is the SCROLLING row and holds the contributed groups only. The accent\r\n CTA is a SIBLING of it, outside the fade mask and outside the scroll container,\r\n because it is the row's anchor \u2014 a call to action that dissolves into a\r\n gradient, or that you have to scroll sideways to reach, inverts the affordance.\r\n It is also what makes `isOverflowing` measurable at all: see the component.\r\n-->\r\n@if (groups().length) {\r\n <div #row class=\"fma\" [class.fma--overflow]=\"isOverflowing()\">\r\n @for (group of groups(); track group.id) {\r\n <div\r\n class=\"fma-group\"\r\n [class.fma-group--framed]=\"group.framed\"\r\n role=\"group\"\r\n [attr.aria-label]=\"i18n.t(group.labelKey)\"\r\n >\r\n @for (action of group.items; track action.id) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"actionButton\"\r\n [ngTemplateOutletContext]=\"{ $implicit: action }\"\r\n />\r\n }\r\n </div>\r\n @if (group.framed) {\r\n <span class=\"fma-sep\" aria-hidden=\"true\"></span>\r\n }\r\n }\r\n </div>\r\n}\r\n\r\n<!--\r\n The accent CTA. The disc is a WRAPPER, not a modifier on the button, and that\r\n is load-bearing rather than cosmetic: `.fma-btn`'s hover/press feedback is an\r\n `animation` (`iconWiggle` / `iconBounce`), and an animation that touches\r\n `transform` beats a declared `transform` \u2014 so a hover lift authored on the\r\n button itself would simply never render. The wrapper owns the disc, the lift\r\n and the shadow; the button stays transparent and keeps the DS's own toolbar\r\n feedback. It re-points `--chrome-ink` rather than styling `.fma-btn` through a\r\n descendant selector so the glyph colour inherits the way custom properties do.\r\n-->\r\n@if (primary(); as p) {\r\n <span class=\"fma-primary\" [class.fma-primary--disabled]=\"p.disabled\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"actionButton\"\r\n [ngTemplateOutletContext]=\"{ $implicit: p }\"\r\n />\r\n </span>\r\n}\r\n\r\n<!--\r\n ONE radio menu, hoisted out of the group loops (S1-review F9). Only one is open\r\n at a time (`openMenuActionId`), so a single instance is enough \u2014 and rendering\r\n it beside its button would insert a non-`.fma-btn` element into the group,\r\n silently shifting every later button's `:nth-child` index and with it the\r\n `toolbarPopIn` stagger delay the `.fma-btn` rule assigns. Keeping every\r\n staggered parent's children homogeneous is what makes that rule's comment true.\r\n It portals itself to <body>, so its position is unaffected by where it is\r\n declared.\r\n-->\r\n@if (openAction(); as open) {\r\n <fly-context-menu\r\n [anchor]=\"menuAnchorEl()\"\r\n [sections]=\"menuSections(open)\"\r\n (action)=\"onMenuAction(open, $event)\"\r\n (closed)=\"closeMenu()\"\r\n />\r\n}\r\n", styles: [":host{display:flex;align-items:center;gap:8px;min-inline-size:0}.fma{display:flex;align-items:center;gap:8px;flex:0 1 auto;min-inline-size:0;overflow:hidden;flex-wrap:nowrap}.fma--overflow{overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none;-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent 100%);mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent 100%)}.fma--overflow::-webkit-scrollbar{display:none}:host([dir=rtl]) .fma--overflow{-webkit-mask-image:linear-gradient(to left,#000 calc(100% - 28px),transparent 100%);mask-image:linear-gradient(to left,#000 calc(100% - 28px),transparent 100%)}.fma-group{display:flex;align-items:center;gap:8px;flex:none}.fma-group--framed{gap:2px}.fma-sep{flex:0 0 auto;inline-size:1px;block-size:18px;background:var(--w14)}.fma-btn{position:relative;flex:0 0 auto;inline-size:30px;block-size:30px;display:grid;place-items:center;border:0;border-radius:50%;background:transparent;color:var(--chrome-ink);cursor:pointer;padding:0;animation:toolbarPopIn .3s var(--nova-ease-structural) both}.fma-btn:nth-child(1){animation-delay:0ms}.fma-btn:nth-child(2){animation-delay:40ms}.fma-btn:nth-child(3){animation-delay:80ms}.fma-btn:nth-child(4){animation-delay:.12s}.fma-btn:nth-child(5){animation-delay:.16s}.fma-btn:nth-child(6){animation-delay:.2s}.fma-btn:nth-child(7){animation-delay:.24s}.fma-btn:nth-child(8){animation-delay:.28s}.fma-btn:nth-child(9){animation-delay:.32s}.fma-btn:nth-child(10){animation-delay:.36s}.fma-btn:nth-child(11){animation-delay:.4s}.fma-btn:nth-child(12){animation-delay:.44s}.fma-btn:nth-child(n+13){animation-delay:.44s}.fma-btn:not([aria-disabled=true]):hover{background:var(--w1);color:var(--chrome-ink-hover);animation:iconWiggle .5s ease}.fma-btn:not([aria-disabled=true]):active{animation:iconBounce .38s var(--nova-ease-overshoot)}.fma-btn:focus-visible{outline:3px solid var(--accent);outline-offset:3px;border-radius:inherit}.fma-btn[data-tone=danger]{color:var(--sys-red)}.fma-btn[data-tone=danger]:not([aria-disabled=true]):hover{background:color-mix(in oklab,var(--sys-red) 18%,transparent);color:var(--sys-red)}.fma-btn[data-tone=success]{color:var(--sys-green)}.fma-btn[data-tone=success]:not([aria-disabled=true]):hover{background:color-mix(in oklab,var(--sys-green) 18%,transparent);color:var(--sys-green)}.fma-btn[aria-pressed=true]{background:var(--tint-sel);color:var(--chrome-ink-hover)}.fma-btn[aria-pressed=true][data-tone=success]{background:color-mix(in oklab,var(--sys-green) 22%,transparent);color:var(--sys-green)}.fma-btn[aria-disabled=true]{opacity:.38;filter:saturate(.6);animation:none;cursor:default}.fma-btn__icon{inline-size:16px;block-size:16px;pointer-events:none}.fma-btn__badge{position:absolute;inset-block-start:3px;inset-inline-end:3px;inline-size:7px;block-size:7px;border-radius:50%;background:var(--accent);pointer-events:none}.fma-primary{--chrome-ink: var(--on-accent);--chrome-ink-hover: var(--on-accent);flex:none;display:grid;place-items:center;inline-size:30px;block-size:30px;border-radius:50%;background:var(--accent);transition:box-shadow var(--nova-duration-hover) var(--nova-ease-micro),transform var(--nova-duration-hover) var(--nova-ease-micro)}.fma-primary:hover{box-shadow:0 8px 20px var(--tint-sel);transform:translateY(-1px)}.fma-primary--disabled{opacity:.38;filter:saturate(.6)}.fma-primary--disabled:hover{box-shadow:none;transform:none}.fma-btn--text{inline-size:auto;block-size:30px;border-radius:99px;padding-inline:12px;font-family:inherit;font-size:12.5px;font-weight:600;line-height:1;white-space:nowrap}@media(pointer:coarse){.fma-btn,.fma-primary{inline-size:44px;block-size:44px}}\n"] }]
16680
+ }, template: "<!--\r\n Icon-only buttons carry the accessible name via [attr.aria-label] \u2014 the SVG\r\n glyph itself is aria-hidden (decorative, skill \u00A7accessibility rule 5). `kind:\r\n 'text'` actions rely on their own visible text instead. Disabled rows stay\r\n `aria-disabled` (never native `disabled`) so they remain focusable/hoverable \u2014\r\n a disabled action's whole point is to explain ITSELF via the auto-suffixed\r\n tooltip, which native `disabled` would suppress.\r\n\r\n The glyph is composed from ALLOWLISTED nodes bound as real attributes, never\r\n from `[innerHTML]`: `iconPath` is publisher-supplied, and an `<svg>` fragment\r\n is not a closed world (`<foreignObject>` is an HTML integration point, `<a\r\n xlink:href=\"javascript:\u2026\">` is live under the very click this button invites).\r\n `magic-actions-icon.ts` carries the full rationale; the shape below is its\r\n consequence \u2014 an attribute binding cannot introduce an element or a handler,\r\n so there is nothing left to sanitize.\r\n\r\n That block is authored ONCE, as `#actionButton`, and stamped by every place a\r\n contributed action is painted (group items and `primary`). It used to be\r\n inline in the group loop, which was fine while this component rendered groups\r\n only; with `primary` here too, a second copy would mean the next edit to the\r\n SECURITY-CRITICAL binding shape has to be made twice and can be made once.\r\n-->\r\n<ng-template #actionButton let-projected>\r\n <!--\r\n `let-projected` is `any` \u2014 Angular infers nothing for an `ngTemplateOutlet`\r\n context, and there is no built-in way to declare one. `asAction` is an\r\n identity function whose only job is to give this block a TYPED local, so\r\n every read below (`.kind`, `.disabled`, `.menu`) is checked by the template\r\n compiler instead of silently resolving to `undefined` on a typo. Without it\r\n the one place a `MagicBarActionView` is actually painted would be the one\r\n place its shape is not verified.\r\n -->\r\n @let action = asAction(projected);\r\n @if (action.kind === 'text') {\r\n <button\r\n type=\"button\"\r\n class=\"fma-btn fma-btn--text\"\r\n [attr.data-tone]=\"toneAttr(action)\"\r\n [attr.aria-disabled]=\"action.disabled ? 'true' : null\"\r\n [attr.aria-pressed]=\"pressedAttr(action)\"\r\n [flyTooltip]=\"tooltipFor(action)\"\r\n [flyTooltipDelay]=\"450\"\r\n (click)=\"activate(action, $event)\"\r\n >{{ resolvedLabel(action) }}</button>\r\n } @else {\r\n <button\r\n type=\"button\"\r\n class=\"fma-btn\"\r\n [class.fma-btn--badge]=\"hasBadge(action)\"\r\n [attr.data-tone]=\"toneAttr(action)\"\r\n [attr.aria-disabled]=\"action.disabled ? 'true' : null\"\r\n [attr.aria-pressed]=\"pressedAttr(action)\"\r\n [attr.aria-haspopup]=\"action.menu ? 'menu' : null\"\r\n [attr.aria-expanded]=\"action.menu ? (openMenuActionId() === action.id ? 'true' : 'false') : null\"\r\n [attr.aria-label]=\"action.menu ? triggerAriaLabel(action) : resolvedLabel(action)\"\r\n [flyTooltip]=\"tooltipFor(action)\"\r\n [flyTooltipDelay]=\"450\"\r\n (click)=\"activate(action, $event)\"\r\n >\r\n <svg\r\n class=\"fma-btn__icon\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"1.8\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n aria-hidden=\"true\"\r\n >\r\n @for (node of iconNodes(action); track $index) {\r\n @switch (node.tag) {\r\n @case ('path') {\r\n <svg:path\r\n [attr.d]=\"node.attrs['d']\"\r\n [attr.fill-rule]=\"node.attrs['fill-rule']\"\r\n [attr.clip-rule]=\"node.attrs['clip-rule']\"\r\n />\r\n }\r\n @case ('circle') {\r\n <svg:circle\r\n [attr.cx]=\"node.attrs['cx']\"\r\n [attr.cy]=\"node.attrs['cy']\"\r\n [attr.r]=\"node.attrs['r']\"\r\n />\r\n }\r\n @case ('rect') {\r\n <svg:rect\r\n [attr.x]=\"node.attrs['x']\"\r\n [attr.y]=\"node.attrs['y']\"\r\n [attr.width]=\"node.attrs['width']\"\r\n [attr.height]=\"node.attrs['height']\"\r\n [attr.rx]=\"node.attrs['rx']\"\r\n [attr.ry]=\"node.attrs['ry']\"\r\n />\r\n }\r\n @case ('ellipse') {\r\n <svg:ellipse\r\n [attr.cx]=\"node.attrs['cx']\"\r\n [attr.cy]=\"node.attrs['cy']\"\r\n [attr.rx]=\"node.attrs['rx']\"\r\n [attr.ry]=\"node.attrs['ry']\"\r\n />\r\n }\r\n @case ('line') {\r\n <svg:line\r\n [attr.x1]=\"node.attrs['x1']\"\r\n [attr.y1]=\"node.attrs['y1']\"\r\n [attr.x2]=\"node.attrs['x2']\"\r\n [attr.y2]=\"node.attrs['y2']\"\r\n />\r\n }\r\n @case ('polyline') {\r\n <svg:polyline [attr.points]=\"node.attrs['points']\" />\r\n }\r\n @case ('polygon') {\r\n <svg:polygon [attr.points]=\"node.attrs['points']\" />\r\n }\r\n }\r\n }\r\n </svg>\r\n @if (hasBadge(action)) {\r\n <span class=\"fma-btn__badge\" aria-hidden=\"true\"></span>\r\n }\r\n </button>\r\n }\r\n</ng-template>\r\n\r\n<!--\r\n `.fma` is the SCROLLING row and holds the contributed groups only. The accent\r\n CTA is a SIBLING of it, outside the fade mask and outside the scroll container,\r\n because it is the row's anchor \u2014 a call to action that dissolves into a\r\n gradient, or that you have to scroll sideways to reach, inverts the affordance.\r\n It is also what makes `isOverflowing` measurable at all: see the component.\r\n-->\r\n@if (groups().length) {\r\n <div #row class=\"fma\" [class.fma--overflow]=\"isOverflowing()\">\r\n @for (group of groups(); track group.id) {\r\n <div\r\n class=\"fma-group\"\r\n [class.fma-group--framed]=\"group.framed\"\r\n role=\"group\"\r\n [attr.aria-label]=\"i18n.t(group.labelKey)\"\r\n >\r\n @for (action of group.items; track action.id) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"actionButton\"\r\n [ngTemplateOutletContext]=\"{ $implicit: action }\"\r\n />\r\n }\r\n </div>\r\n @if (group.framed) {\r\n <span class=\"fma-sep\" aria-hidden=\"true\"></span>\r\n }\r\n }\r\n </div>\r\n}\r\n\r\n<!--\r\n The accent CTA. The disc is a WRAPPER, not a modifier on the button, and that\r\n is load-bearing rather than cosmetic: `.fma-btn`'s hover/press feedback is an\r\n `animation` (`iconWiggle` / `iconBounce`), and an animation that touches\r\n `transform` beats a declared `transform` \u2014 so a hover lift authored on the\r\n button itself would simply never render. The wrapper owns the disc, the lift\r\n and the shadow; the button stays transparent and keeps the DS's own toolbar\r\n feedback. It re-points `--chrome-ink` rather than styling `.fma-btn` through a\r\n descendant selector so the glyph colour inherits the way custom properties do.\r\n-->\r\n@if (primary(); as p) {\r\n <span class=\"fma-primary\" [class.fma-primary--disabled]=\"p.disabled\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"actionButton\"\r\n [ngTemplateOutletContext]=\"{ $implicit: p }\"\r\n />\r\n </span>\r\n}\r\n\r\n<!--\r\n ONE radio menu, hoisted out of the group loops (S1-review F9). Only one is open\r\n at a time (`openMenuActionId`), so a single instance is enough \u2014 and rendering\r\n it beside its button would insert a non-`.fma-btn` element into the group,\r\n silently shifting every later button's `:nth-child` index and with it the\r\n `toolbarPopIn` stagger delay the `.fma-btn` rule assigns. Keeping every\r\n staggered parent's children homogeneous is what makes that rule's comment true.\r\n It portals itself to <body>, so its position is unaffected by where it is\r\n declared.\r\n-->\r\n@if (openAction(); as open) {\r\n <fly-context-menu\r\n [anchor]=\"menuAnchorEl()\"\r\n [sections]=\"menuSections(open)\"\r\n (action)=\"onMenuAction(open, $event)\"\r\n (closed)=\"closeMenu()\"\r\n />\r\n}\r\n", styles: [":host{display:flex;align-items:center;gap:8px;min-inline-size:0}.fma{display:flex;align-items:center;gap:8px;flex:0 1 auto;min-inline-size:0;overflow:hidden;flex-wrap:nowrap}.fma--overflow{overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none;-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent 100%);mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent 100%)}.fma--overflow::-webkit-scrollbar{display:none}:host([dir=rtl]) .fma--overflow{-webkit-mask-image:linear-gradient(to left,#000 calc(100% - 28px),transparent 100%);mask-image:linear-gradient(to left,#000 calc(100% - 28px),transparent 100%)}.fma-group{display:flex;align-items:center;gap:8px;flex:none}.fma-group--framed{gap:2px}.fma-sep{flex:0 0 auto;inline-size:1px;block-size:18px;background:var(--w14)}.fma-btn{position:relative;flex:0 0 auto;inline-size:30px;block-size:30px;display:grid;place-items:center;border:0;border-radius:50%;background:transparent;color:var(--chrome-ink);cursor:pointer;padding:0;animation:toolbarPopIn .3s var(--nova-ease-structural) both}.fma-btn:nth-child(1){animation-delay:0ms}.fma-btn:nth-child(2){animation-delay:40ms}.fma-btn:nth-child(3){animation-delay:80ms}.fma-btn:nth-child(4){animation-delay:.12s}.fma-btn:nth-child(5){animation-delay:.16s}.fma-btn:nth-child(6){animation-delay:.2s}.fma-btn:nth-child(7){animation-delay:.24s}.fma-btn:nth-child(8){animation-delay:.28s}.fma-btn:nth-child(9){animation-delay:.32s}.fma-btn:nth-child(10){animation-delay:.36s}.fma-btn:nth-child(11){animation-delay:.4s}.fma-btn:nth-child(12){animation-delay:.44s}.fma-btn:nth-child(n+13){animation-delay:.44s}.fma-btn:not([aria-disabled=true]):hover{background:var(--w1);color:var(--chrome-ink-hover);animation:iconWiggle .5s ease}.fma-btn:not([aria-disabled=true]):active{animation:iconBounce .38s var(--nova-ease-overshoot)}.fma-btn:focus-visible{outline:3px solid var(--accent);outline-offset:3px;border-radius:inherit}.fma-btn[data-tone=danger]{color:var(--sys-red)}.fma-btn[data-tone=danger]:not([aria-disabled=true]):hover{background:color-mix(in oklab,var(--sys-red) 18%,transparent);color:var(--sys-red)}.fma-btn[data-tone=success]{color:var(--sys-green)}.fma-btn[data-tone=success]:not([aria-disabled=true]):hover{background:color-mix(in oklab,var(--sys-green) 18%,transparent);color:var(--sys-green)}.fma-btn[aria-pressed=true]{background:var(--tint-sel);color:var(--chrome-ink-hover)}.fma-btn[aria-pressed=true][data-tone=success]{background:color-mix(in oklab,var(--sys-green) 22%,transparent);color:var(--sys-green)}.fma-btn[aria-disabled=true]{opacity:.38;filter:saturate(.6);animation:none;cursor:default}.fma-btn__icon{inline-size:16px;block-size:16px;pointer-events:none}.fma-btn__badge{position:absolute;inset-block-start:3px;inset-inline-end:3px;inline-size:7px;block-size:7px;border-radius:50%;background:var(--accent);pointer-events:none}.fma-primary{--chrome-ink: var(--on-accent);--chrome-ink-hover: var(--on-accent);flex:none;display:grid;place-items:center;inline-size:30px;block-size:30px;border-radius:50%;background:var(--accent);transition:box-shadow var(--nova-duration-hover) var(--nova-ease-micro),transform var(--nova-duration-hover) var(--nova-ease-micro)}.fma-primary:hover{box-shadow:0 8px 20px var(--tint-sel);transform:translateY(-1px)}.fma-primary--disabled{opacity:.38;filter:saturate(.6)}.fma-primary--disabled:hover{box-shadow:none;transform:none}.fma-btn--text{inline-size:auto;block-size:30px;border-radius:99px;padding-inline:12px;font-family:inherit;font-size:12.5px;font-weight:600;line-height:1;white-space:nowrap}@media(pointer:coarse){.fma-btn{inline-size:44px;block-size:44px}.fma-btn--text{inline-size:auto;min-inline-size:44px}.fma-primary{inline-size:44px;block-size:44px}}\n"] }]
16554
16681
  }], ctorParameters: () => [], propDecorators: { groups: [{ type: i0.Input, args: [{ isSignal: true, alias: "groups", required: true }] }], primary: [{ type: i0.Input, args: [{ isSignal: true, alias: "primary", required: false }] }], rowRef: [{ type: i0.ViewChild, args: ['row', { isSignal: true }] }] } });
16555
16682
 
16556
16683
  /**
@@ -16621,6 +16748,13 @@ const ORIGINAL_SAME_CONVENTION = {
16621
16748
  assignRobot: '<rect x="4" y="8" width="16" height="11" rx="3"/><path d="M12 8V4"/><circle cx="12" cy="3" r="1.2"/><path d="M8 13v2M16 13v2"/>',
16622
16749
  /** Task-detail "Add subtask". */
16623
16750
  addSubtask: '<path d="M9 6h11M9 12h11M9 18h7"/><path d="M4 6h.01M4 12h.01"/><path d="M4.5 16h3M6 14.5v3"/>',
16751
+ /**
16752
+ * Calendar magic-bar "View" trigger (S4.2) — a 4-cell grid standing in for the
16753
+ * year/month/week/day/agenda mode switcher behind it. No standalone glyph for
16754
+ * this exists in the UX drop's curated set (`today`/`settings` cover Today and
16755
+ * the settings gear, not the mode switcher).
16756
+ */
16757
+ calendarView: '<rect x="3.5" y="3.5" width="17" height="17" rx="2.5"/><path d="M3.5 12h17M12 3.5v17"/>',
16624
16758
  };
16625
16759
  /**
16626
16760
  * The full curated table. Publishers reference it by name
@@ -18543,6 +18677,21 @@ function unwrapCurrencies(res) {
18543
18677
  * i18n is self-sufficient through the `currency_selector.*` keys in `DS_BASELINE_LOCALES`
18544
18678
  * (en/ar/fr/ur); RTL works via logical CSS.
18545
18679
  *
18680
+ * ## `locked`, vs `disabled`
18681
+ * `disabled` is UI convention for "not applicable right now" — greyed out, no explanation,
18682
+ * because none is owed (a form section that only exists once a prior step completes, say).
18683
+ * `locked` is a different claim entirely: **the value is fixed on purpose**, because
18684
+ * something downstream now depends on it (PPM freezes a project's currency the moment any
18685
+ * financial row exists — changing it would silently re-denominate every stored amount).
18686
+ * A dimmed control with no explanation is exactly the failure `skills/magic-bar-actions.md`
18687
+ * §3.6 names for actions ("a vanishing action teaches the user nothing"); the same argument
18688
+ * applies to a frozen field. So `locked` renders differently on purpose: full-opacity (never
18689
+ * dimmed — the value is not "unavailable", it is authoritative), no dropdown affordance at
18690
+ * all, and an always-visible reason caption instead of a hover-only tooltip, so the "why" is
18691
+ * legible to a screen reader without requiring focus and to a sighted user without hovering.
18692
+ * `locked` takes precedence when both are set — it is the more specific state and the
18693
+ * `disabled` trigger markup (with its dropdown affordances) never renders underneath it.
18694
+ *
18546
18695
  * @example
18547
18696
  * ```html
18548
18697
  * <!-- Loads /api/currencies/brief itself: -->
@@ -18553,6 +18702,12 @@ function unwrapCurrencies(res) {
18553
18702
  * mode="multi"
18554
18703
  * [allowedCodes]="tenantCurrencies()"
18555
18704
  * (selectionDetailChange)="onCurrenciesPicked($event)" />
18705
+ *
18706
+ * <!-- Frozen once the project has financial rows — reason is an i18n KEY, never text: -->
18707
+ * <fly-currency-selector
18708
+ * [(ngModel)]="project.currency"
18709
+ * [locked]="project.hasFinancialRows"
18710
+ * lockedReasonKey="projects.currency_locked_reason" />
18556
18711
  * ```
18557
18712
  */
18558
18713
  class FlyCurrencySelectorComponent {
@@ -18575,6 +18730,24 @@ class FlyCurrencySelectorComponent {
18575
18730
  pinnedCodes = input([], ...(ngDevMode ? [{ debugName: "pinnedCodes" }] : /* istanbul ignore next */ []));
18576
18731
  /** Disable the whole control (also driven by reactive-forms `setDisabledState`). */
18577
18732
  disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
18733
+ /**
18734
+ * Freeze the current selection because something downstream now depends on it — a
18735
+ * DIFFERENT claim than `disabled`. See the class doc's "`locked`, vs `disabled`"
18736
+ * section. Renders the current pick as a plain, non-interactive readout (no dropdown
18737
+ * affordance at all) plus an always-visible reason caption — never a dimmed clickable-
18738
+ * looking control. Takes precedence over `disabled` when both are set.
18739
+ */
18740
+ locked = input(false, ...(ngDevMode ? [{ debugName: "locked" }] : /* istanbul ignore next */ []));
18741
+ /**
18742
+ * i18n KEY (never resolved text — see `skills/magic-bar-actions.md` §3.4) explaining
18743
+ * WHY the control is locked. Omit to use the localized `currency_selector.locked_default_reason`
18744
+ * baseline key; supply your own only when that default reason is wrong for your case
18745
+ * (mirrors `MagicBarActionSpec.disabledTipKey`'s "supply only when the default is wrong"
18746
+ * contract). Ignored while `locked` is `false`.
18747
+ */
18748
+ lockedReasonKey = input(null, ...(ngDevMode ? [{ debugName: "lockedReasonKey" }] : /* istanbul ignore next */ []));
18749
+ /** `I18nService.t()` params for `lockedReasonKey`, e.g. `{ date: frozenOn }`. */
18750
+ lockedReasonParams = input(undefined, ...(ngDevMode ? [{ debugName: "lockedReasonParams" }] : /* istanbul ignore next */ []));
18578
18751
  /** Show the trigger clear (✕) affordance when there is a selection. */
18579
18752
  clearable = input(true, ...(ngDevMode ? [{ debugName: "clearable" }] : /* istanbul ignore next */ []));
18580
18753
  /** Trigger text when nothing is picked. Omit for the localized default. */
@@ -18594,6 +18767,8 @@ class FlyCurrencySelectorComponent {
18594
18767
  _uid = ++_flyCurrencySelectorUid;
18595
18768
  listboxId = `fly-currency-selector-${this._uid}-listbox`;
18596
18769
  triggerId = `fly-currency-selector-${this._uid}-trigger`;
18770
+ /** id of the always-rendered locked-reason caption; `aria-describedby` target of the locked readout. */
18771
+ lockedReasonId = `fly-currency-selector-${this._uid}-locked-reason`;
18597
18772
  isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
18598
18773
  searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : /* istanbul ignore next */ []));
18599
18774
  activeIndex = signal(0, ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
@@ -18634,6 +18809,8 @@ class FlyCurrencySelectorComponent {
18634
18809
  clearText = computed(() => this._i18n.t('currency_selector.clear'), ...(ngDevMode ? [{ debugName: "clearText" }] : /* istanbul ignore next */ []));
18635
18810
  pinnedGroupText = computed(() => this._i18n.t('currency_selector.pinned'), ...(ngDevMode ? [{ debugName: "pinnedGroupText" }] : /* istanbul ignore next */ []));
18636
18811
  allGroupText = computed(() => this._i18n.t('currency_selector.all'), ...(ngDevMode ? [{ debugName: "allGroupText" }] : /* istanbul ignore next */ []));
18812
+ /** Resolved locked-reason text — the default baseline key, or the host's `lockedReasonKey`. */
18813
+ lockedReasonText = computed(() => this._i18n.t(this.lockedReasonKey() ?? 'currency_selector.locked_default_reason', this.lockedReasonParams()), ...(ngDevMode ? [{ debugName: "lockedReasonText" }] : /* istanbul ignore next */ []));
18637
18814
  /** Every offered row, after the `allowedCodes` restriction. */
18638
18815
  available = computed(() => {
18639
18816
  const rows = this.currencies() ?? this._loaded();
@@ -18713,7 +18890,7 @@ class FlyCurrencySelectorComponent {
18713
18890
  }
18714
18891
  // ── Open / close ───────────────────────────────────────────────────────────
18715
18892
  toggleOpen() {
18716
- if (this.effectiveDisabled())
18893
+ if (this.effectiveDisabled() || this.locked())
18717
18894
  return;
18718
18895
  if (this.isOpen())
18719
18896
  this.close();
@@ -18721,7 +18898,7 @@ class FlyCurrencySelectorComponent {
18721
18898
  this.open();
18722
18899
  }
18723
18900
  open() {
18724
- if (this.effectiveDisabled() || this.isOpen())
18901
+ if (this.effectiveDisabled() || this.locked() || this.isOpen())
18725
18902
  return;
18726
18903
  this.isOpen.set(true);
18727
18904
  this.activeIndex.set(0);
@@ -18740,7 +18917,7 @@ class FlyCurrencySelectorComponent {
18740
18917
  }
18741
18918
  // ── Selection ──────────────────────────────────────────────────────────────
18742
18919
  pick(currency) {
18743
- if (this.effectiveDisabled())
18920
+ if (this.effectiveDisabled() || this.locked())
18744
18921
  return;
18745
18922
  if (!this.isMulti()) {
18746
18923
  this._commit([currency.code]);
@@ -18755,13 +18932,13 @@ class FlyCurrencySelectorComponent {
18755
18932
  }
18756
18933
  remove(code, event) {
18757
18934
  event?.stopPropagation();
18758
- if (this.effectiveDisabled())
18935
+ if (this.effectiveDisabled() || this.locked())
18759
18936
  return;
18760
18937
  this._commit(this._selectedCodes().filter((c) => c.toUpperCase() !== code.toUpperCase()));
18761
18938
  }
18762
18939
  clear(event) {
18763
18940
  event?.stopPropagation();
18764
- if (this.effectiveDisabled())
18941
+ if (this.effectiveDisabled() || this.locked())
18765
18942
  return;
18766
18943
  this._commit([]);
18767
18944
  }
@@ -18874,13 +19051,13 @@ class FlyCurrencySelectorComponent {
18874
19051
  this._onTouched();
18875
19052
  }
18876
19053
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyCurrencySelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
18877
- 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 }, 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()" }, classAttribute: "fly-currency-selector" }, providers: [
19054
+ 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: [
18878
19055
  {
18879
19056
  provide: NG_VALUE_ACCESSOR,
18880
19057
  useExisting: forwardRef(() => FlyCurrencySelectorComponent),
18881
19058
  multi: true,
18882
19059
  },
18883
- ], viewQueries: [{ propertyName: "searchEl", first: true, predicate: ["searchRef"], descendants: true }, { propertyName: "triggerEl", first: true, predicate: ["triggerRef"], descendants: true }], ngImport: i0, template: "<div\r\n class=\"fly-currency-selector__wrap\"\r\n [flyClickOutsideEnabled]=\"isOpen()\"\r\n (flyClickOutside)=\"close()\"\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</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__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"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: FlyClickOutsideDirective, selector: "[flyClickOutside]", inputs: ["flyClickOutsideEnabled"], outputs: ["flyClickOutside"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
19060
+ ], viewQueries: [{ propertyName: "searchEl", first: true, predicate: ["searchRef"], descendants: true }, { propertyName: "triggerEl", first: true, predicate: ["triggerRef"], descendants: true }], ngImport: i0, 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"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: FlyClickOutsideDirective, selector: "[flyClickOutside]", inputs: ["flyClickOutsideEnabled"], outputs: ["flyClickOutside"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
18884
19061
  }
18885
19062
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyCurrencySelectorComponent, decorators: [{
18886
19063
  type: Component,
@@ -18894,8 +19071,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
18894
19071
  class: 'fly-currency-selector',
18895
19072
  '[class.fly-currency-selector--open]': 'isOpen()',
18896
19073
  '[class.fly-currency-selector--disabled]': 'effectiveDisabled()',
18897
- }, template: "<div\r\n class=\"fly-currency-selector__wrap\"\r\n [flyClickOutsideEnabled]=\"isOpen()\"\r\n (flyClickOutside)=\"close()\"\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</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__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"] }]
18898
- }], 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 }] }], 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: [{
19074
+ '[class.fly-currency-selector--locked]': 'locked()',
19075
+ }, 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"] }]
19076
+ }], 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: [{
18899
19077
  type: ViewChild,
18900
19078
  args: ['searchRef']
18901
19079
  }], triggerEl: [{
@@ -18904,152 +19082,580 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
18904
19082
  }] } });
18905
19083
 
18906
19084
  /**
18907
- * Document-wide open-overlay stackEscape arbitration for stacked overlays.
18908
- *
18909
- * Every layered overlay (drawer, modal, confirm dialog, menu, popover) listens for
18910
- * Escape at the document level, so without arbitration one keypress closes the whole
18911
- * pile — a confirm dialog opened over a drawer takes the drawer down with it on the
18912
- * first Escape. Each overlay pushes a handle when it opens and removes it when it
18913
- * closes or is destroyed; its Escape handler acts only while its handle is
18914
- * top-of-stack. First Escape then closes the confirm, second the drawer.
18915
- *
18916
- * The stack is pure (no DOM, no DI) so the ordering rules are unit-testable, and the
18917
- * shared {@link overlayStack} singleton spans the whole document — which is the point:
18918
- * independently-owned overlays, including ones in different federated remotes, must
18919
- * arbitrate globally or they cannot know about each other.
18920
- *
18921
- * ## Federation
18922
- * Native Federation shares this package as a singleton, so every app in the shell
18923
- * binds to one stack instance. A remote that forks its own DS copy would get its own
18924
- * stack and lose arbitration against shell overlays — that is what the package's
18925
- * federation singleton guard exists to catch.
19085
+ * Locale-aware formatting primitivesnumbers, byte sizes, relative time, and dates.
18926
19086
  *
18927
- * @example
18928
- * ```ts
18929
- * export class MyDrawer {
18930
- * private handle: OverlayHandle | null = null;
19087
+ * All of these are `Intl`-backed and take an explicit `locale`, which is the whole
19088
+ * point: the estate is full of hand-rolled formatters that hardcode ASCII digits,
19089
+ * English unit strings ("1.4 MB", "5m ago"), or the *browser's* default locale rather
19090
+ * than the app's selected language. Under `ar` / `ur` those render wrong — and i18n is
19091
+ * mandatory on this platform, so this is a correctness surface, not a convenience one.
18931
19092
  *
18932
- * open() { this.handle = overlayStack.push(); }
18933
- * close() { this.handle = overlayStack.remove(this.handle); }
19093
+ * Pure functions here; the `| flyCompact`-style pipes in `format.pipes.ts` wrap them
19094
+ * and default the locale to the active {@link I18nService} language.
18934
19095
  *
18935
- * onEscape() { if (overlayStack.isTop(this.handle)) this.close(); }
18936
- * }
18937
- * ```
19096
+ * ## The em-dash convention
19097
+ * Every formatter returns `'—'` (U+2014) for null / undefined / non-finite input rather
19098
+ * than throwing, `'NaN'`, or an empty string. A visible placeholder keeps table columns
19099
+ * aligned and makes "no value" legible; an empty string reads as a rendering bug.
18938
19100
  */
18939
- class OverlayStack {
18940
- stack = [];
18941
- /** Registers an opening overlay; the returned handle identifies it for later calls. */
18942
- push() {
18943
- const handle = { overlay: true };
18944
- this.stack.push(handle);
18945
- return handle;
18946
- }
18947
- /**
18948
- * Unregisters a closing or destroyed overlay. Tolerates out-of-order removal (an
18949
- * inner overlay torn down after its parent), unknown handles, and `null`.
18950
- *
18951
- * Returns `null` so callers can clear their field in one statement:
18952
- * `this.handle = overlayStack.remove(this.handle)`.
18953
- */
18954
- remove(handle) {
18955
- if (!handle)
18956
- return null;
18957
- const index = this.stack.indexOf(handle);
18958
- if (index !== -1)
18959
- this.stack.splice(index, 1);
18960
- return null;
18961
- }
18962
- /** True when this handle is the topmost open overlay — its Escape handler may act. */
18963
- isTop(handle) {
18964
- return (handle !== null &&
18965
- this.stack.length > 0 &&
18966
- this.stack[this.stack.length - 1] === handle);
19101
+ /** Rendered for null / undefined / non-finite input across every formatter here. */
19102
+ const FLY_EMPTY_VALUE = '—';
19103
+ // ─── Numbers ─────────────────────────────────────────────────────────────────
19104
+ /** At or above this magnitude, switch from grouped-exact to compact (K/M/B) notation. */
19105
+ const FLY_COMPACT_THRESHOLD = 10_000;
19106
+ // Intl formatters are expensive to construct relative to how often these are called in
19107
+ // a table cell or chart label; cache one per distinct configuration.
19108
+ const compactCache = new Map();
19109
+ const integerCache = new Map();
19110
+ const decimalCache = new Map();
19111
+ function compactFormatter(locale) {
19112
+ let f = compactCache.get(locale);
19113
+ if (!f) {
19114
+ f = new Intl.NumberFormat(locale, { notation: 'compact', maximumFractionDigits: 1 });
19115
+ compactCache.set(locale, f);
18967
19116
  }
18968
- /** Number of currently-open overlays. Useful for scroll-lock refcounting. */
18969
- get depth() {
18970
- return this.stack.length;
19117
+ return f;
19118
+ }
19119
+ function integerFormatter(locale) {
19120
+ let f = integerCache.get(locale);
19121
+ if (!f) {
19122
+ f = new Intl.NumberFormat(locale, { maximumFractionDigits: 0 });
19123
+ integerCache.set(locale, f);
18971
19124
  }
19125
+ return f;
18972
19126
  }
18973
19127
  /**
18974
- * The shared, document-wide stack. Import this rather than constructing an
18975
- * `OverlayStack` a private instance cannot arbitrate against anyone else's
18976
- * overlays, which defeats the purpose.
18977
- */
18978
- const overlayStack = new OverlayStack();
18979
-
18980
- /**
18981
- * Focus capture/restore for overlays — the other half of a correct dismiss story.
18982
- *
18983
- * When an overlay opens it moves focus inside itself; when it closes, focus must go
18984
- * back to whatever opened it, or the keyboard user is dumped at the top of the
18985
- * document and has to re-traverse the page. Every hand-rolled modal/drawer in the
18986
- * estate re-implements this with a `restoreFocusTo` field and a `.focus()` call, and
18987
- * most of them miss at least one of the edge cases below.
18988
- *
18989
- * @example
18990
- * ```ts
18991
- * private restore: FocusRestore | null = null;
19128
+ * Compact, scannable form grouped-exact below {@link FLY_COMPACT_THRESHOLD},
19129
+ * unit-compacted above it.
18992
19130
  *
18993
- * open() { this.restore = captureFocus(); }
18994
- * close() { this.restore = restoreFocus(this.restore); }
18995
19131
  * ```
19132
+ * 6 042 → "6,042" (exact; small buckets read best as real numbers)
19133
+ * 12 400 → "12.4K"
19134
+ * 1 900 787 → "1.9M"
19135
+ * 2 300 000 000 → "2.3B"
19136
+ * ```
19137
+ *
19138
+ * Pair with {@link flyFullNumber} in a tooltip or `aria-label` so the exact figure is
19139
+ * always one hover away — compaction is a display affordance, not data loss.
18996
19140
  */
19141
+ function flyCompactNumber(value, locale = 'en') {
19142
+ if (value == null || !Number.isFinite(value))
19143
+ return FLY_EMPTY_VALUE;
19144
+ return Math.abs(value) >= FLY_COMPACT_THRESHOLD
19145
+ ? compactFormatter(locale).format(value)
19146
+ : integerFormatter(locale).format(value);
19147
+ }
19148
+ /** Exact, fully-grouped integer form for tooltips / a11y (e.g. `"1,900,787"`). */
19149
+ function flyFullNumber(value, locale = 'en') {
19150
+ if (value == null || !Number.isFinite(value))
19151
+ return FLY_EMPTY_VALUE;
19152
+ return integerFormatter(locale).format(value);
19153
+ }
18997
19154
  /**
18998
- * Snapshots the currently-focused element so it can be refocused later.
18999
- *
19000
- * Returns a token even when nothing is focused (`document.body` is treated as "no
19001
- * meaningful focus"), so callers never branch — {@link restoreFocus} no-ops on it.
19155
+ * Fractional form for ratios where the decimal *is* the signal (avg depth 1.5,
19156
+ * sparsity 0.25) — distinct from {@link flyFullNumber}, which floors to integers.
19157
+ * Trailing zeros drop.
19002
19158
  */
19003
- function captureFocus(doc = document) {
19004
- const active = doc.activeElement;
19005
- const element = active instanceof HTMLElement && active !== doc.body ? active : null;
19006
- return { element };
19159
+ function flyDecimalNumber(value, locale = 'en', maxFractionDigits = 2) {
19160
+ if (value == null || !Number.isFinite(value))
19161
+ return FLY_EMPTY_VALUE;
19162
+ const key = `${locale}|${maxFractionDigits}`;
19163
+ let f = decimalCache.get(key);
19164
+ if (!f) {
19165
+ f = new Intl.NumberFormat(locale, { maximumFractionDigits: maxFractionDigits });
19166
+ decimalCache.set(key, f);
19167
+ }
19168
+ return f.format(value);
19007
19169
  }
19008
19170
  /**
19009
- * Returns focus to the captured element, if it is still focusable.
19010
- *
19011
- * Guards the three cases that make naive `restoreFocusTo.focus()` misbehave:
19012
- * - the element was removed from the DOM while the overlay was open (a row deleted by
19013
- * the very dialog that is closing) — `isConnected` is false, so we skip rather than
19014
- * throw focus to `<body>` via a detached node;
19015
- * - it became disabled or `inert` while the overlay was open;
19016
- * - nothing was focused when the overlay opened.
19017
- *
19018
- * `preventScroll` keeps the page from jumping when the trigger has scrolled out of
19019
- * view behind the overlay — the caller decides whether the trigger should be scrolled
19020
- * back into view, which is a product decision, not a focus one.
19171
+ * Signed compact delta for "change since last poll" chips (`"+12.4K"`, `"−340"`).
19172
+ * Returns an empty string for zero / non-finite so a no-change chip renders nothing
19173
+ * rather than a meaningless "0".
19021
19174
  *
19022
- * Returns `null` so callers can clear their field in one statement.
19175
+ * Uses U+2212 MINUS SIGN, not a hyphen it matches the plus glyph's width and weight,
19176
+ * so a column of deltas stays visually aligned.
19023
19177
  */
19024
- function restoreFocus(restore, options = {}) {
19025
- const element = restore?.element;
19026
- if (!element || !element.isConnected)
19027
- return null;
19028
- if (element.hasAttribute('disabled') || element.closest('[inert]'))
19029
- return null;
19030
- element.focus({ preventScroll: options.preventScroll ?? true });
19031
- return null;
19178
+ function flySignedCompact(delta, locale = 'en') {
19179
+ if (!Number.isFinite(delta) || delta === 0)
19180
+ return '';
19181
+ const sign = delta > 0 ? '+' : '−';
19182
+ return sign + flyCompactNumber(Math.abs(delta), locale);
19032
19183
  }
19033
-
19184
+ // ─── Byte sizes ──────────────────────────────────────────────────────────────
19185
+ /** Binary unit ladder. Byte sizes are conventionally base-1024 in file UIs. */
19186
+ const BYTE_UNITS = ['byte', 'kilobyte', 'megabyte', 'gigabyte', 'terabyte'];
19187
+ const byteCache = new Map();
19034
19188
  /**
19035
- * Debounce primitives the shared replacement for the `setTimeout` / `clearTimeout`
19036
- * pairs hand-rolled in every list screen and typeahead across the estate.
19189
+ * Human-readable byte size (`"482 bytes"`, `"1.4 MB"`, `"2.3 GB"`), localized.
19037
19190
  *
19038
- * Two entry points, for the two situations:
19039
- * - {@link FlyDebouncer} an object you hold and call, with `cancel()` and `flush()`.
19040
- * Use it in a component that debounces on a field (search boxes, reload-on-filter).
19041
- * - {@link flyDebounced} — an injection-context factory that wires `cancel()` to the
19042
- * host's `DestroyRef` for you, so a pending call can never fire after teardown.
19191
+ * Base-1024 with a log-derived unit pick, clamped at TB so a bogus huge value degrades
19192
+ * to a large TB figure instead of overflowing the ladder. Trailing zeros drop
19193
+ * (`1.0 MB` `1 MB`).
19043
19194
  *
19044
- * ## Why not `debounceTime` from RxJS
19045
- * Nothing wrong with it when the input is already a stream. But the common case here
19046
- * is a signal-based component with an `(input)` handler and no Subject in sight, and
19047
- * standing up a `Subject` + `takeUntilDestroyed` + `subscribe` to debounce one field is
19048
- * more machinery than the problem deserves. These helpers are the imperative
19049
- * equivalent, with the teardown correctness that hand-rolled timers usually miss.
19195
+ * Localization matters here and is the reason this supersedes the eight hand-rolled
19196
+ * copies in the estate: those concatenate hardcoded English unit strings, so an Arabic
19197
+ * user saw Latin "MB" beside Arabic-Indic digits. `Intl` unit formatting renders both
19198
+ * the number and the unit in the active locale (French even gets "octets").
19050
19199
  *
19051
- * ## The leak these fix
19052
- * A bare `setTimeout(() => this.reload(), 150)` with no `clearTimeout` in `ngOnDestroy`
19200
+ * Note the deliberate convention mismatch: the maths is base-1024 while the rendered
19201
+ * symbols are the SI ones ("kB", "MB"), so 1024 bytes shows as "1 kB" rather than the
19202
+ * pedantically-correct "1 KiB". Every mainstream file UI — Windows Explorer, Finder —
19203
+ * does exactly this, and `Intl` has no binary-prefix units, so matching user
19204
+ * expectation beats matching the standard here.
19205
+ */
19206
+ function flyFormatBytes(bytes, locale = 'en') {
19207
+ if (bytes == null || !Number.isFinite(bytes))
19208
+ return FLY_EMPTY_VALUE;
19209
+ if (bytes <= 0)
19210
+ return formatByteValue(0, 'byte', locale);
19211
+ const index = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), BYTE_UNITS.length - 1);
19212
+ const value = bytes / Math.pow(1024, index);
19213
+ // Bytes are whole things — never render "482.3 B".
19214
+ const rounded = index === 0 ? Math.round(value) : parseFloat(value.toFixed(1));
19215
+ return formatByteValue(rounded, BYTE_UNITS[index], locale);
19216
+ }
19217
+ function formatByteValue(value, unit, locale) {
19218
+ const key = `${locale}|${unit}`;
19219
+ let f = byteCache.get(key);
19220
+ if (!f) {
19221
+ f = new Intl.NumberFormat(locale, {
19222
+ style: 'unit',
19223
+ unit,
19224
+ // Raw bytes read best spelled out and pluralized ("482 bytes", "1 byte" —
19225
+ // and correctly "482 octets" in French). The larger units are universally
19226
+ // recognised as symbols, where the spelled-out form ("1.4 megabytes") would
19227
+ // be noise in a file list.
19228
+ unitDisplay: unit === 'byte' ? 'long' : 'short',
19229
+ maximumFractionDigits: 1,
19230
+ });
19231
+ byteCache.set(key, f);
19232
+ }
19233
+ return f.format(value);
19234
+ }
19235
+ // ─── Relative time ───────────────────────────────────────────────────────────
19236
+ /**
19237
+ * Formats an instant as a localized relative age — `"2 minutes ago"` /
19238
+ * `"منذ دقيقتين"` / `"il y a 2 minutes"`. Works for future instants too
19239
+ * (`"in 3 days"`), which is what makes it usable for due dates and SLA countdowns.
19240
+ *
19241
+ * `now` is injectable so tests can pin the clock instead of sleeping or accepting
19242
+ * flake around bucket boundaries.
19243
+ */
19244
+ function flyRelativeTime(value, locale = 'en', now = new Date()) {
19245
+ if (value == null || value === '')
19246
+ return FLY_EMPTY_VALUE;
19247
+ const then = value instanceof Date ? value : new Date(value);
19248
+ const ms = then.getTime();
19249
+ if (Number.isNaN(ms))
19250
+ return FLY_EMPTY_VALUE;
19251
+ const diffMs = ms - now.getTime();
19252
+ const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
19253
+ const absSec = Math.abs(diffMs) / 1000;
19254
+ // `numeric: 'auto'` is what yields "yesterday" / "now" instead of a stiff
19255
+ // "1 day ago" / "0 seconds ago" at the bucket edges.
19256
+ if (absSec < 60)
19257
+ return rtf.format(Math.round(diffMs / 1000), 'second');
19258
+ if (absSec < 3600)
19259
+ return rtf.format(Math.round(diffMs / 60_000), 'minute');
19260
+ if (absSec < 86_400)
19261
+ return rtf.format(Math.round(diffMs / 3_600_000), 'hour');
19262
+ if (absSec < 2_592_000)
19263
+ return rtf.format(Math.round(diffMs / 86_400_000), 'day');
19264
+ if (absSec < 31_536_000)
19265
+ return rtf.format(Math.round(diffMs / 2_592_000_000), 'month');
19266
+ return rtf.format(Math.round(diffMs / 31_536_000_000), 'year');
19267
+ }
19268
+ /**
19269
+ * Formats a duration in seconds as a localized short unit string — `420` → `"7 min"`
19270
+ * (en) / `"7 د"` (ar). Picks seconds / minutes / hours by magnitude.
19271
+ */
19272
+ function flyDuration(seconds, locale = 'en') {
19273
+ if (seconds == null || !Number.isFinite(seconds) || seconds < 0) {
19274
+ return FLY_EMPTY_VALUE;
19275
+ }
19276
+ const fmt = (unit, v) => new Intl.NumberFormat(locale, {
19277
+ style: 'unit',
19278
+ unit,
19279
+ unitDisplay: 'narrow',
19280
+ maximumFractionDigits: 0,
19281
+ }).format(v);
19282
+ if (seconds < 60)
19283
+ return fmt('second', seconds);
19284
+ if (seconds < 3600)
19285
+ return fmt('minute', seconds / 60);
19286
+ return fmt('hour', seconds / 3600);
19287
+ }
19288
+ // ─── Dates ───────────────────────────────────────────────────────────────────
19289
+ /**
19290
+ * Localized calendar date (no time). Pass `options` to override the default
19291
+ * short-date presentation.
19292
+ *
19293
+ * Unlike the bare `toLocaleDateString()` calls it replaces, this takes an explicit
19294
+ * locale — those used the *browser's* locale and so ignored the app's language setting
19295
+ * entirely. Invalid input degrades to the em dash rather than `"Invalid Date"`.
19296
+ */
19297
+ function flyFormatDate(value, locale = 'en', options = { dateStyle: 'medium' }) {
19298
+ const date = toValidDate(value);
19299
+ if (!date)
19300
+ return FLY_EMPTY_VALUE;
19301
+ try {
19302
+ return new Intl.DateTimeFormat(locale, options).format(date);
19303
+ }
19304
+ catch {
19305
+ // A malformed `options` object (or an unsupported locale extension) throws;
19306
+ // degrade to the ISO date rather than taking the view down.
19307
+ return date.toISOString().slice(0, 10);
19308
+ }
19309
+ }
19310
+ /** Localized time of day (no date). */
19311
+ function flyFormatTime(value, locale = 'en', options = { timeStyle: 'short' }) {
19312
+ return flyFormatDate(value, locale, options);
19313
+ }
19314
+ /** Localized date + time — the tooltip companion to a relative or short-date cell. */
19315
+ function flyFormatDateTime(value, locale = 'en', options = { dateStyle: 'medium', timeStyle: 'short' }) {
19316
+ return flyFormatDate(value, locale, options);
19317
+ }
19318
+ /**
19319
+ * Calendar date as `yyyy-MM-dd` — the wire/sort form, deliberately NOT localized.
19320
+ *
19321
+ * Use for `<input type="date">` values, query params, and sort keys. For anything a
19322
+ * user reads, use {@link flyFormatDate}.
19323
+ *
19324
+ * Derived from the instant's **UTC** date so the value is stable across timezones —
19325
+ * a local-date derivation shifts the day for users east/west of the source data.
19326
+ */
19327
+ function flyToDateOnly(value) {
19328
+ const date = toValidDate(value);
19329
+ return date ? date.toISOString().slice(0, 10) : '';
19330
+ }
19331
+ function toValidDate(value) {
19332
+ if (value == null || value === '')
19333
+ return null;
19334
+ const date = value instanceof Date ? value : new Date(value);
19335
+ return Number.isNaN(date.getTime()) ? null : date;
19336
+ }
19337
+
19338
+ /**
19339
+ * ISO 4217 minor-unit EXCEPTIONS, used ONLY when `currency` is a bare code string with
19340
+ * no matching `FlyCurrency` row to read `decimalDigits` from. This is deliberately a
19341
+ * PARTIAL table — it mirrors exactly the two exception groups
19342
+ * {@link FlyCurrency.decimalDigits}'s own doc comment names (0-decimal: JPY, KRW, the
19343
+ * CFA francs; 3-decimal: the seven Gulf/MENA dinars/rials), not full ISO 4217 coverage.
19344
+ * Every code outside these two sets defaults to 2, which is correct for the ISO 4217
19345
+ * majority.
19346
+ *
19347
+ * This is the "explicit, documented fallback" D-B7-2 requires in place of a silent
19348
+ * "assume 2" — but it is still a fallback, not a substitute for the real data. A caller
19349
+ * that needs certainty for every currency (there are 0-decimal codes beyond the three
19350
+ * listed here) should pass the `FlyCurrency` row instead of a bare code:
19351
+ * `fly-currency-selector`'s `(selectionDetailChange)` emits the full row precisely so a
19352
+ * host never has to guess.
19353
+ */
19354
+ const ZERO_DECIMAL_FALLBACK_CODES = new Set(['JPY', 'KRW', 'XAF', 'XOF', 'XPF']);
19355
+ const THREE_DECIMAL_FALLBACK_CODES = new Set([
19356
+ 'BHD',
19357
+ 'IQD',
19358
+ 'JOD',
19359
+ 'KWD',
19360
+ 'LYD',
19361
+ 'OMR',
19362
+ 'TND',
19363
+ ]);
19364
+ function fallbackDecimalDigits(code) {
19365
+ if (ZERO_DECIMAL_FALLBACK_CODES.has(code))
19366
+ return 0;
19367
+ if (THREE_DECIMAL_FALLBACK_CODES.has(code))
19368
+ return 3;
19369
+ return 2;
19370
+ }
19371
+ /** Accepts a `FlyCurrency` row or a bare ISO 4217 code string; `null`/`undefined`/blank resolves to `null`. */
19372
+ function resolveCurrencyMeta(currency) {
19373
+ if (currency && typeof currency === 'object') {
19374
+ const digits = Number.isInteger(currency.decimalDigits) && currency.decimalDigits >= 0
19375
+ ? currency.decimalDigits
19376
+ : 2; // Defensive only — a malformed row should still render something rather than throw.
19377
+ return { code: currency.code.trim().toUpperCase(), symbol: currency.symbol ?? null, decimalDigits: digits };
19378
+ }
19379
+ if (typeof currency === 'string' && currency.trim()) {
19380
+ const code = currency.trim().toUpperCase();
19381
+ return { code, symbol: null, decimalDigits: fallbackDecimalDigits(code) };
19382
+ }
19383
+ return null;
19384
+ }
19385
+ /** The text substituted for Intl's `currency` part, per `display`. `null` removes it entirely. */
19386
+ function displayLabel(meta, display) {
19387
+ if (display === 'none')
19388
+ return null;
19389
+ if (display === 'code')
19390
+ return meta.code;
19391
+ // 'symbol' — degrade to the code when no symbol is known (the bare-code path), never
19392
+ // an Intl-guessed symbol that might not match the platform's own catalogue.
19393
+ return meta.symbol && meta.symbol.trim() ? meta.symbol : meta.code;
19394
+ }
19395
+ // Intl.NumberFormat construction is non-trivial; cache one per distinct (locale, code, digits).
19396
+ const moneyFormatterCache = new Map();
19397
+ /**
19398
+ * Returns `null` when `code` is not well-formed enough for `Intl` to accept as a
19399
+ * `currency` option (e.g. a malformed catalogue row) — the caller degrades to
19400
+ * {@link manualFormat} rather than letting a `RangeError` take the view down.
19401
+ */
19402
+ function currencyFormatter(locale, code, digits) {
19403
+ const key = `${locale}|${code}|${digits}`;
19404
+ const cached = moneyFormatterCache.get(key);
19405
+ if (cached)
19406
+ return cached;
19407
+ try {
19408
+ const f = new Intl.NumberFormat(locale, {
19409
+ style: 'currency',
19410
+ currency: code,
19411
+ // Always 'code', regardless of the caller's `display` option — see the module
19412
+ // doc comment on why this is the structural template rather than 'symbol'.
19413
+ currencyDisplay: 'code',
19414
+ minimumFractionDigits: digits,
19415
+ maximumFractionDigits: digits,
19416
+ });
19417
+ moneyFormatterCache.set(key, f);
19418
+ return f;
19419
+ }
19420
+ catch {
19421
+ return null;
19422
+ }
19423
+ }
19424
+ const plainDecimalCache = new Map();
19425
+ function plainDecimalFormatter(locale, digits) {
19426
+ const key = `${locale}|${digits}`;
19427
+ let f = plainDecimalCache.get(key);
19428
+ if (!f) {
19429
+ f = new Intl.NumberFormat(locale, { minimumFractionDigits: digits, maximumFractionDigits: digits });
19430
+ plainDecimalCache.set(key, f);
19431
+ }
19432
+ return f;
19433
+ }
19434
+ /** Manual `"<label> <number>"` construction for the rare case Intl rejects the code outright. */
19435
+ function manualFormat(amount, meta, display, locale) {
19436
+ const number = plainDecimalFormatter(locale, meta.decimalDigits).format(amount);
19437
+ const label = displayLabel(meta, display);
19438
+ return label ? `${label} ${number}` : number;
19439
+ }
19440
+ /**
19441
+ * Formats `amount` in `currency`, localized to `options.locale`.
19442
+ *
19443
+ * ```ts
19444
+ * flyFormatMoney(1234.5, kwdRow) // "د.ك 1,234.500" (KWD is 3-decimal)
19445
+ * flyFormatMoney(1234, 'JPY') // "JPY 1,234" (bare code, no symbol to read — degrades to the code)
19446
+ * flyFormatMoney(-42, usdRow, { display: 'code' }) // "-USD 42.00"
19447
+ * flyFormatMoney(99.9, eurRow, { display: 'none' }) // "99.90" (currency named elsewhere on screen)
19448
+ * flyFormatMoney(null, usdRow) // "—" (never "NaN")
19449
+ * ```
19450
+ *
19451
+ * `amount == null` or non-finite (`NaN`, `Infinity`), or an unresolvable `currency`,
19452
+ * renders {@link FLY_EMPTY_VALUE} — the same placeholder every other `format.ts`
19453
+ * primitive uses, so a money cell in a mixed table degrades exactly like its neighbours
19454
+ * instead of introducing a second "no value" convention.
19455
+ */
19456
+ function flyFormatMoney(amount, currency, options = {}) {
19457
+ if (amount == null || !Number.isFinite(amount))
19458
+ return FLY_EMPTY_VALUE;
19459
+ const meta = resolveCurrencyMeta(currency);
19460
+ if (!meta)
19461
+ return FLY_EMPTY_VALUE;
19462
+ const { display = 'symbol', locale = 'en' } = options;
19463
+ const formatter = currencyFormatter(locale, meta.code, meta.decimalDigits);
19464
+ if (!formatter)
19465
+ return manualFormat(amount, meta, display, locale);
19466
+ const label = displayLabel(meta, display);
19467
+ return formatter
19468
+ .formatToParts(amount)
19469
+ .map((part) => (part.type === 'currency' ? (label ?? '') : part.value))
19470
+ // Removing the currency part (display: 'none') leaves its adjacent literal
19471
+ // separator behind (Intl emits currency+space as two parts) — collapse and trim
19472
+ // rather than special-casing every locale's separator placement.
19473
+ .join('')
19474
+ .replace(/\s+/g, ' ')
19475
+ .trim();
19476
+ }
19477
+
19478
+ /**
19479
+ * `{{ amount | flyMoney: currencyRow }}` → `"$1,234.50"` / `{{ amount | flyMoney: 'KWD' }}`
19480
+ * → `"KD 1,234.500"`.
19481
+ *
19482
+ * Template wrapper over {@link flyFormatMoney}, defaulting the locale to the active
19483
+ * {@link I18nService} language the same way every other `Fly*Pipe` in `format.pipes.ts`
19484
+ * does — see that file's doc comment for the "pass the locale signal explicitly in a
19485
+ * view that must react live to the language switcher" caveat, which applies here too.
19486
+ *
19487
+ * A separate file from `format.pipes.ts` on purpose: this pipe's `currency` argument
19488
+ * depends on `FlyCurrency` (the currency-selector's data contract), a domain type the
19489
+ * pure number/byte/date formatters in `format.pipes.ts` have no reason to import.
19490
+ *
19491
+ * ```html
19492
+ * {{ invoice.total | flyMoney: invoice.currency }}
19493
+ * {{ invoice.total | flyMoney: invoice.currency : { display: 'code' } }}
19494
+ * ```
19495
+ */
19496
+ class FlyMoneyPipe {
19497
+ i18n = inject(I18nService);
19498
+ transform(amount, currency, options) {
19499
+ return flyFormatMoney(amount, currency, {
19500
+ display: options?.display,
19501
+ locale: options?.locale ?? this.i18n.locale(),
19502
+ });
19503
+ }
19504
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyMoneyPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
19505
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.19", ngImport: i0, type: FlyMoneyPipe, isStandalone: true, name: "flyMoney" });
19506
+ }
19507
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyMoneyPipe, decorators: [{
19508
+ type: Pipe,
19509
+ args: [{ name: 'flyMoney', standalone: true }]
19510
+ }] });
19511
+
19512
+ /**
19513
+ * Document-wide open-overlay stack — Escape arbitration for stacked overlays.
19514
+ *
19515
+ * Every layered overlay (drawer, modal, confirm dialog, menu, popover) listens for
19516
+ * Escape at the document level, so without arbitration one keypress closes the whole
19517
+ * pile — a confirm dialog opened over a drawer takes the drawer down with it on the
19518
+ * first Escape. Each overlay pushes a handle when it opens and removes it when it
19519
+ * closes or is destroyed; its Escape handler acts only while its handle is
19520
+ * top-of-stack. First Escape then closes the confirm, second the drawer.
19521
+ *
19522
+ * The stack is pure (no DOM, no DI) so the ordering rules are unit-testable, and the
19523
+ * shared {@link overlayStack} singleton spans the whole document — which is the point:
19524
+ * independently-owned overlays, including ones in different federated remotes, must
19525
+ * arbitrate globally or they cannot know about each other.
19526
+ *
19527
+ * ## Federation
19528
+ * Native Federation shares this package as a singleton, so every app in the shell
19529
+ * binds to one stack instance. A remote that forks its own DS copy would get its own
19530
+ * stack and lose arbitration against shell overlays — that is what the package's
19531
+ * federation singleton guard exists to catch.
19532
+ *
19533
+ * @example
19534
+ * ```ts
19535
+ * export class MyDrawer {
19536
+ * private handle: OverlayHandle | null = null;
19537
+ *
19538
+ * open() { this.handle = overlayStack.push(); }
19539
+ * close() { this.handle = overlayStack.remove(this.handle); }
19540
+ *
19541
+ * onEscape() { if (overlayStack.isTop(this.handle)) this.close(); }
19542
+ * }
19543
+ * ```
19544
+ */
19545
+ class OverlayStack {
19546
+ stack = [];
19547
+ /** Registers an opening overlay; the returned handle identifies it for later calls. */
19548
+ push() {
19549
+ const handle = { overlay: true };
19550
+ this.stack.push(handle);
19551
+ return handle;
19552
+ }
19553
+ /**
19554
+ * Unregisters a closing or destroyed overlay. Tolerates out-of-order removal (an
19555
+ * inner overlay torn down after its parent), unknown handles, and `null`.
19556
+ *
19557
+ * Returns `null` so callers can clear their field in one statement:
19558
+ * `this.handle = overlayStack.remove(this.handle)`.
19559
+ */
19560
+ remove(handle) {
19561
+ if (!handle)
19562
+ return null;
19563
+ const index = this.stack.indexOf(handle);
19564
+ if (index !== -1)
19565
+ this.stack.splice(index, 1);
19566
+ return null;
19567
+ }
19568
+ /** True when this handle is the topmost open overlay — its Escape handler may act. */
19569
+ isTop(handle) {
19570
+ return (handle !== null &&
19571
+ this.stack.length > 0 &&
19572
+ this.stack[this.stack.length - 1] === handle);
19573
+ }
19574
+ /** Number of currently-open overlays. Useful for scroll-lock refcounting. */
19575
+ get depth() {
19576
+ return this.stack.length;
19577
+ }
19578
+ }
19579
+ /**
19580
+ * The shared, document-wide stack. Import this rather than constructing an
19581
+ * `OverlayStack` — a private instance cannot arbitrate against anyone else's
19582
+ * overlays, which defeats the purpose.
19583
+ */
19584
+ const overlayStack = new OverlayStack();
19585
+
19586
+ /**
19587
+ * Focus capture/restore for overlays — the other half of a correct dismiss story.
19588
+ *
19589
+ * When an overlay opens it moves focus inside itself; when it closes, focus must go
19590
+ * back to whatever opened it, or the keyboard user is dumped at the top of the
19591
+ * document and has to re-traverse the page. Every hand-rolled modal/drawer in the
19592
+ * estate re-implements this with a `restoreFocusTo` field and a `.focus()` call, and
19593
+ * most of them miss at least one of the edge cases below.
19594
+ *
19595
+ * @example
19596
+ * ```ts
19597
+ * private restore: FocusRestore | null = null;
19598
+ *
19599
+ * open() { this.restore = captureFocus(); }
19600
+ * close() { this.restore = restoreFocus(this.restore); }
19601
+ * ```
19602
+ */
19603
+ /**
19604
+ * Snapshots the currently-focused element so it can be refocused later.
19605
+ *
19606
+ * Returns a token even when nothing is focused (`document.body` is treated as "no
19607
+ * meaningful focus"), so callers never branch — {@link restoreFocus} no-ops on it.
19608
+ */
19609
+ function captureFocus(doc = document) {
19610
+ const active = doc.activeElement;
19611
+ const element = active instanceof HTMLElement && active !== doc.body ? active : null;
19612
+ return { element };
19613
+ }
19614
+ /**
19615
+ * Returns focus to the captured element, if it is still focusable.
19616
+ *
19617
+ * Guards the three cases that make naive `restoreFocusTo.focus()` misbehave:
19618
+ * - the element was removed from the DOM while the overlay was open (a row deleted by
19619
+ * the very dialog that is closing) — `isConnected` is false, so we skip rather than
19620
+ * throw focus to `<body>` via a detached node;
19621
+ * - it became disabled or `inert` while the overlay was open;
19622
+ * - nothing was focused when the overlay opened.
19623
+ *
19624
+ * `preventScroll` keeps the page from jumping when the trigger has scrolled out of
19625
+ * view behind the overlay — the caller decides whether the trigger should be scrolled
19626
+ * back into view, which is a product decision, not a focus one.
19627
+ *
19628
+ * Returns `null` so callers can clear their field in one statement.
19629
+ */
19630
+ function restoreFocus(restore, options = {}) {
19631
+ const element = restore?.element;
19632
+ if (!element || !element.isConnected)
19633
+ return null;
19634
+ if (element.hasAttribute('disabled') || element.closest('[inert]'))
19635
+ return null;
19636
+ element.focus({ preventScroll: options.preventScroll ?? true });
19637
+ return null;
19638
+ }
19639
+
19640
+ /**
19641
+ * Debounce primitives — the shared replacement for the `setTimeout` / `clearTimeout`
19642
+ * pairs hand-rolled in every list screen and typeahead across the estate.
19643
+ *
19644
+ * Two entry points, for the two situations:
19645
+ * - {@link FlyDebouncer} — an object you hold and call, with `cancel()` and `flush()`.
19646
+ * Use it in a component that debounces on a field (search boxes, reload-on-filter).
19647
+ * - {@link flyDebounced} — an injection-context factory that wires `cancel()` to the
19648
+ * host's `DestroyRef` for you, so a pending call can never fire after teardown.
19649
+ *
19650
+ * ## Why not `debounceTime` from RxJS
19651
+ * Nothing wrong with it when the input is already a stream. But the common case here
19652
+ * is a signal-based component with an `(input)` handler and no Subject in sight, and
19653
+ * standing up a `Subject` + `takeUntilDestroyed` + `subscribe` to debounce one field is
19654
+ * more machinery than the problem deserves. These helpers are the imperative
19655
+ * equivalent, with the teardown correctness that hand-rolled timers usually miss.
19656
+ *
19657
+ * ## The leak these fix
19658
+ * A bare `setTimeout(() => this.reload(), 150)` with no `clearTimeout` in `ngOnDestroy`
19053
19659
  * still fires after the component is gone — reading destroyed signals, calling a
19054
19660
  * service on a torn-down injector, or logging a spurious HTTP request. {@link FlyDebouncer}
19055
19661
  * makes cancellation available and {@link flyDebounced} makes it automatic.
@@ -19725,260 +20331,6 @@ function presenceColorFor(seed) {
19725
20331
  return PRESENCE_COLORS[Math.abs(hash) % PRESENCE_COLORS.length];
19726
20332
  }
19727
20333
 
19728
- /**
19729
- * Locale-aware formatting primitives — numbers, byte sizes, relative time, and dates.
19730
- *
19731
- * All of these are `Intl`-backed and take an explicit `locale`, which is the whole
19732
- * point: the estate is full of hand-rolled formatters that hardcode ASCII digits,
19733
- * English unit strings ("1.4 MB", "5m ago"), or the *browser's* default locale rather
19734
- * than the app's selected language. Under `ar` / `ur` those render wrong — and i18n is
19735
- * mandatory on this platform, so this is a correctness surface, not a convenience one.
19736
- *
19737
- * Pure functions here; the `| flyCompact`-style pipes in `format.pipes.ts` wrap them
19738
- * and default the locale to the active {@link I18nService} language.
19739
- *
19740
- * ## The em-dash convention
19741
- * Every formatter returns `'—'` (U+2014) for null / undefined / non-finite input rather
19742
- * than throwing, `'NaN'`, or an empty string. A visible placeholder keeps table columns
19743
- * aligned and makes "no value" legible; an empty string reads as a rendering bug.
19744
- */
19745
- /** Rendered for null / undefined / non-finite input across every formatter here. */
19746
- const FLY_EMPTY_VALUE = '—';
19747
- // ─── Numbers ─────────────────────────────────────────────────────────────────
19748
- /** At or above this magnitude, switch from grouped-exact to compact (K/M/B) notation. */
19749
- const FLY_COMPACT_THRESHOLD = 10_000;
19750
- // Intl formatters are expensive to construct relative to how often these are called in
19751
- // a table cell or chart label; cache one per distinct configuration.
19752
- const compactCache = new Map();
19753
- const integerCache = new Map();
19754
- const decimalCache = new Map();
19755
- function compactFormatter(locale) {
19756
- let f = compactCache.get(locale);
19757
- if (!f) {
19758
- f = new Intl.NumberFormat(locale, { notation: 'compact', maximumFractionDigits: 1 });
19759
- compactCache.set(locale, f);
19760
- }
19761
- return f;
19762
- }
19763
- function integerFormatter(locale) {
19764
- let f = integerCache.get(locale);
19765
- if (!f) {
19766
- f = new Intl.NumberFormat(locale, { maximumFractionDigits: 0 });
19767
- integerCache.set(locale, f);
19768
- }
19769
- return f;
19770
- }
19771
- /**
19772
- * Compact, scannable form — grouped-exact below {@link FLY_COMPACT_THRESHOLD},
19773
- * unit-compacted above it.
19774
- *
19775
- * ```
19776
- * 6 042 → "6,042" (exact; small buckets read best as real numbers)
19777
- * 12 400 → "12.4K"
19778
- * 1 900 787 → "1.9M"
19779
- * 2 300 000 000 → "2.3B"
19780
- * ```
19781
- *
19782
- * Pair with {@link flyFullNumber} in a tooltip or `aria-label` so the exact figure is
19783
- * always one hover away — compaction is a display affordance, not data loss.
19784
- */
19785
- function flyCompactNumber(value, locale = 'en') {
19786
- if (value == null || !Number.isFinite(value))
19787
- return FLY_EMPTY_VALUE;
19788
- return Math.abs(value) >= FLY_COMPACT_THRESHOLD
19789
- ? compactFormatter(locale).format(value)
19790
- : integerFormatter(locale).format(value);
19791
- }
19792
- /** Exact, fully-grouped integer form for tooltips / a11y (e.g. `"1,900,787"`). */
19793
- function flyFullNumber(value, locale = 'en') {
19794
- if (value == null || !Number.isFinite(value))
19795
- return FLY_EMPTY_VALUE;
19796
- return integerFormatter(locale).format(value);
19797
- }
19798
- /**
19799
- * Fractional form for ratios where the decimal *is* the signal (avg depth 1.5,
19800
- * sparsity 0.25) — distinct from {@link flyFullNumber}, which floors to integers.
19801
- * Trailing zeros drop.
19802
- */
19803
- function flyDecimalNumber(value, locale = 'en', maxFractionDigits = 2) {
19804
- if (value == null || !Number.isFinite(value))
19805
- return FLY_EMPTY_VALUE;
19806
- const key = `${locale}|${maxFractionDigits}`;
19807
- let f = decimalCache.get(key);
19808
- if (!f) {
19809
- f = new Intl.NumberFormat(locale, { maximumFractionDigits: maxFractionDigits });
19810
- decimalCache.set(key, f);
19811
- }
19812
- return f.format(value);
19813
- }
19814
- /**
19815
- * Signed compact delta for "change since last poll" chips (`"+12.4K"`, `"−340"`).
19816
- * Returns an empty string for zero / non-finite so a no-change chip renders nothing
19817
- * rather than a meaningless "0".
19818
- *
19819
- * Uses U+2212 MINUS SIGN, not a hyphen — it matches the plus glyph's width and weight,
19820
- * so a column of deltas stays visually aligned.
19821
- */
19822
- function flySignedCompact(delta, locale = 'en') {
19823
- if (!Number.isFinite(delta) || delta === 0)
19824
- return '';
19825
- const sign = delta > 0 ? '+' : '−';
19826
- return sign + flyCompactNumber(Math.abs(delta), locale);
19827
- }
19828
- // ─── Byte sizes ──────────────────────────────────────────────────────────────
19829
- /** Binary unit ladder. Byte sizes are conventionally base-1024 in file UIs. */
19830
- const BYTE_UNITS = ['byte', 'kilobyte', 'megabyte', 'gigabyte', 'terabyte'];
19831
- const byteCache = new Map();
19832
- /**
19833
- * Human-readable byte size (`"482 bytes"`, `"1.4 MB"`, `"2.3 GB"`), localized.
19834
- *
19835
- * Base-1024 with a log-derived unit pick, clamped at TB so a bogus huge value degrades
19836
- * to a large TB figure instead of overflowing the ladder. Trailing zeros drop
19837
- * (`1.0 MB` → `1 MB`).
19838
- *
19839
- * Localization matters here and is the reason this supersedes the eight hand-rolled
19840
- * copies in the estate: those concatenate hardcoded English unit strings, so an Arabic
19841
- * user saw Latin "MB" beside Arabic-Indic digits. `Intl` unit formatting renders both
19842
- * the number and the unit in the active locale (French even gets "octets").
19843
- *
19844
- * Note the deliberate convention mismatch: the maths is base-1024 while the rendered
19845
- * symbols are the SI ones ("kB", "MB"), so 1024 bytes shows as "1 kB" rather than the
19846
- * pedantically-correct "1 KiB". Every mainstream file UI — Windows Explorer, Finder —
19847
- * does exactly this, and `Intl` has no binary-prefix units, so matching user
19848
- * expectation beats matching the standard here.
19849
- */
19850
- function flyFormatBytes(bytes, locale = 'en') {
19851
- if (bytes == null || !Number.isFinite(bytes))
19852
- return FLY_EMPTY_VALUE;
19853
- if (bytes <= 0)
19854
- return formatByteValue(0, 'byte', locale);
19855
- const index = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), BYTE_UNITS.length - 1);
19856
- const value = bytes / Math.pow(1024, index);
19857
- // Bytes are whole things — never render "482.3 B".
19858
- const rounded = index === 0 ? Math.round(value) : parseFloat(value.toFixed(1));
19859
- return formatByteValue(rounded, BYTE_UNITS[index], locale);
19860
- }
19861
- function formatByteValue(value, unit, locale) {
19862
- const key = `${locale}|${unit}`;
19863
- let f = byteCache.get(key);
19864
- if (!f) {
19865
- f = new Intl.NumberFormat(locale, {
19866
- style: 'unit',
19867
- unit,
19868
- // Raw bytes read best spelled out and pluralized ("482 bytes", "1 byte" —
19869
- // and correctly "482 octets" in French). The larger units are universally
19870
- // recognised as symbols, where the spelled-out form ("1.4 megabytes") would
19871
- // be noise in a file list.
19872
- unitDisplay: unit === 'byte' ? 'long' : 'short',
19873
- maximumFractionDigits: 1,
19874
- });
19875
- byteCache.set(key, f);
19876
- }
19877
- return f.format(value);
19878
- }
19879
- // ─── Relative time ───────────────────────────────────────────────────────────
19880
- /**
19881
- * Formats an instant as a localized relative age — `"2 minutes ago"` /
19882
- * `"منذ دقيقتين"` / `"il y a 2 minutes"`. Works for future instants too
19883
- * (`"in 3 days"`), which is what makes it usable for due dates and SLA countdowns.
19884
- *
19885
- * `now` is injectable so tests can pin the clock instead of sleeping or accepting
19886
- * flake around bucket boundaries.
19887
- */
19888
- function flyRelativeTime(value, locale = 'en', now = new Date()) {
19889
- if (value == null || value === '')
19890
- return FLY_EMPTY_VALUE;
19891
- const then = value instanceof Date ? value : new Date(value);
19892
- const ms = then.getTime();
19893
- if (Number.isNaN(ms))
19894
- return FLY_EMPTY_VALUE;
19895
- const diffMs = ms - now.getTime();
19896
- const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
19897
- const absSec = Math.abs(diffMs) / 1000;
19898
- // `numeric: 'auto'` is what yields "yesterday" / "now" instead of a stiff
19899
- // "1 day ago" / "0 seconds ago" at the bucket edges.
19900
- if (absSec < 60)
19901
- return rtf.format(Math.round(diffMs / 1000), 'second');
19902
- if (absSec < 3600)
19903
- return rtf.format(Math.round(diffMs / 60_000), 'minute');
19904
- if (absSec < 86_400)
19905
- return rtf.format(Math.round(diffMs / 3_600_000), 'hour');
19906
- if (absSec < 2_592_000)
19907
- return rtf.format(Math.round(diffMs / 86_400_000), 'day');
19908
- if (absSec < 31_536_000)
19909
- return rtf.format(Math.round(diffMs / 2_592_000_000), 'month');
19910
- return rtf.format(Math.round(diffMs / 31_536_000_000), 'year');
19911
- }
19912
- /**
19913
- * Formats a duration in seconds as a localized short unit string — `420` → `"7 min"`
19914
- * (en) / `"7 د"` (ar). Picks seconds / minutes / hours by magnitude.
19915
- */
19916
- function flyDuration(seconds, locale = 'en') {
19917
- if (seconds == null || !Number.isFinite(seconds) || seconds < 0) {
19918
- return FLY_EMPTY_VALUE;
19919
- }
19920
- const fmt = (unit, v) => new Intl.NumberFormat(locale, {
19921
- style: 'unit',
19922
- unit,
19923
- unitDisplay: 'narrow',
19924
- maximumFractionDigits: 0,
19925
- }).format(v);
19926
- if (seconds < 60)
19927
- return fmt('second', seconds);
19928
- if (seconds < 3600)
19929
- return fmt('minute', seconds / 60);
19930
- return fmt('hour', seconds / 3600);
19931
- }
19932
- // ─── Dates ───────────────────────────────────────────────────────────────────
19933
- /**
19934
- * Localized calendar date (no time). Pass `options` to override the default
19935
- * short-date presentation.
19936
- *
19937
- * Unlike the bare `toLocaleDateString()` calls it replaces, this takes an explicit
19938
- * locale — those used the *browser's* locale and so ignored the app's language setting
19939
- * entirely. Invalid input degrades to the em dash rather than `"Invalid Date"`.
19940
- */
19941
- function flyFormatDate(value, locale = 'en', options = { dateStyle: 'medium' }) {
19942
- const date = toValidDate(value);
19943
- if (!date)
19944
- return FLY_EMPTY_VALUE;
19945
- try {
19946
- return new Intl.DateTimeFormat(locale, options).format(date);
19947
- }
19948
- catch {
19949
- // A malformed `options` object (or an unsupported locale extension) throws;
19950
- // degrade to the ISO date rather than taking the view down.
19951
- return date.toISOString().slice(0, 10);
19952
- }
19953
- }
19954
- /** Localized time of day (no date). */
19955
- function flyFormatTime(value, locale = 'en', options = { timeStyle: 'short' }) {
19956
- return flyFormatDate(value, locale, options);
19957
- }
19958
- /** Localized date + time — the tooltip companion to a relative or short-date cell. */
19959
- function flyFormatDateTime(value, locale = 'en', options = { dateStyle: 'medium', timeStyle: 'short' }) {
19960
- return flyFormatDate(value, locale, options);
19961
- }
19962
- /**
19963
- * Calendar date as `yyyy-MM-dd` — the wire/sort form, deliberately NOT localized.
19964
- *
19965
- * Use for `<input type="date">` values, query params, and sort keys. For anything a
19966
- * user reads, use {@link flyFormatDate}.
19967
- *
19968
- * Derived from the instant's **UTC** date so the value is stable across timezones —
19969
- * a local-date derivation shifts the day for users east/west of the source data.
19970
- */
19971
- function flyToDateOnly(value) {
19972
- const date = toValidDate(value);
19973
- return date ? date.toISOString().slice(0, 10) : '';
19974
- }
19975
- function toValidDate(value) {
19976
- if (value == null || value === '')
19977
- return null;
19978
- const date = value instanceof Date ? value : new Date(value);
19979
- return Number.isNaN(date.getTime()) ? null : date;
19980
- }
19981
-
19982
20334
  /**
19983
20335
  * Template wrappers over the `format.ts` primitives. Each defaults its locale to the
19984
20336
  * active {@link I18nService} language, so the common case is `{{ value | flyBytes }}`
@@ -20295,7 +20647,7 @@ class FlyStateMessageComponent {
20295
20647
  </button>
20296
20648
  }
20297
20649
  <ng-content />
20298
- `, isInline: true, styles: [":host{display:flex;flex-direction:column;align-items:center;gap:var(--sp-3);padding:48px;color:var(--ink-3);font-size:var(--text-lg)}.msg__icon{font-size:var(--text-lg)}.msg__text{margin:0}:host([data-kind=empty]){padding:56px 24px;gap:11px}:host([data-kind=empty]) .msg__icon{display:grid;place-items:center;inline-size:46px;block-size:46px;border-radius:16px;background:var(--w07);border:1px solid var(--w12);color:var(--w45);font-size:21px}:host([data-kind=empty]) .msg__text{font:600 14px/1.3 var(--font-sans);color:var(--w8)}.msg__note{margin:0;max-inline-size:320px;font:400 12.5px/1.5 var(--font-sans);color:var(--w45);text-wrap:pretty}:host([data-kind=success]) .msg__icon{color:var(--success)}:host([data-kind=warning]) .msg__icon{color:var(--warning)}:host([data-kind=error]){flex-flow:row wrap;justify-content:flex-start;padding:var(--sp-3) var(--sp-4);margin-block-start:var(--sp-3);border-radius:var(--r-lg);background:var(--danger-bg);border:1px solid var(--danger-line);color:var(--ink);font-size:var(--text-base)}:host([data-kind=error]) .msg__icon{color:var(--danger)}.msg__retry{display:inline-flex;align-items:center;justify-content:center;gap:var(--sp-2);font-family:inherit;font-size:var(--text-base);font-weight:var(--fw-medium);border-radius:var(--r-md);border:1px solid transparent;cursor:pointer;transition:background var(--t-state),border-color var(--t-state),color var(--t-state),box-shadow var(--t-state),filter var(--t-state)}.msg__retry:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.msg__retry:disabled,.msg__retry[aria-disabled=true]{opacity:.5;cursor:not-allowed;pointer-events:none}.msg__retry{height:28px;padding-inline:var(--sp-3);font-size:var(--text-sm);background:var(--bg-2);border-color:var(--line);color:var(--ink-2)}.msg__retry:hover{background:var(--bg-3);border-color:var(--line-2);color:var(--ink)}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
20650
+ `, isInline: true, styles: [":host{display:flex;flex-direction:column;align-items:center;gap:var(--sp-3);padding:48px;color:var(--ink-3);font-size:var(--text-lg)}.msg__icon{font-size:var(--text-lg)}.msg__text{margin:0}:host([data-kind=empty]){padding:56px 24px;gap:11px}:host([data-kind=empty]) .msg__icon{display:grid;place-items:center;inline-size:46px;block-size:46px;border-radius:16px;background:var(--fill-tertiary);border:1px solid var(--surface-border);color:var(--label-secondary);font-size:21px}:host([data-kind=empty]) .msg__text{font:600 14px/1.3 var(--font-sans);color:var(--label-primary)}.msg__note{margin:0;max-inline-size:320px;font:400 12.5px/1.5 var(--font-sans);color:var(--label-secondary);text-wrap:pretty}:host([data-kind=success]) .msg__icon{color:var(--success)}:host([data-kind=warning]) .msg__icon{color:var(--warning)}:host([data-kind=error]){flex-flow:row wrap;justify-content:flex-start;padding:var(--sp-3) var(--sp-4);margin-block-start:var(--sp-3);border-radius:var(--r-lg);background:var(--danger-bg);border:1px solid var(--danger-line);color:var(--ink);font-size:var(--text-base)}:host([data-kind=error]) .msg__icon{color:var(--danger)}.msg__retry{display:inline-flex;align-items:center;justify-content:center;gap:var(--sp-2);font-family:inherit;font-size:var(--text-base);font-weight:var(--fw-medium);border-radius:var(--r-md);border:1px solid transparent;cursor:pointer;transition:background var(--t-state),border-color var(--t-state),color var(--t-state),box-shadow var(--t-state),filter var(--t-state)}.msg__retry:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.msg__retry:disabled,.msg__retry[aria-disabled=true]{opacity:.5;cursor:not-allowed;pointer-events:none}.msg__retry{height:28px;padding-inline:var(--sp-3);font-size:var(--text-sm);background:var(--bg-2);border-color:var(--line);color:var(--ink-2)}.msg__retry:hover{background:var(--bg-3);border-color:var(--line-2);color:var(--ink)}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
20299
20651
  }
20300
20652
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyStateMessageComponent, decorators: [{
20301
20653
  type: Component,
@@ -20317,7 +20669,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
20317
20669
  </button>
20318
20670
  }
20319
20671
  <ng-content />
20320
- `, styles: [":host{display:flex;flex-direction:column;align-items:center;gap:var(--sp-3);padding:48px;color:var(--ink-3);font-size:var(--text-lg)}.msg__icon{font-size:var(--text-lg)}.msg__text{margin:0}:host([data-kind=empty]){padding:56px 24px;gap:11px}:host([data-kind=empty]) .msg__icon{display:grid;place-items:center;inline-size:46px;block-size:46px;border-radius:16px;background:var(--w07);border:1px solid var(--w12);color:var(--w45);font-size:21px}:host([data-kind=empty]) .msg__text{font:600 14px/1.3 var(--font-sans);color:var(--w8)}.msg__note{margin:0;max-inline-size:320px;font:400 12.5px/1.5 var(--font-sans);color:var(--w45);text-wrap:pretty}:host([data-kind=success]) .msg__icon{color:var(--success)}:host([data-kind=warning]) .msg__icon{color:var(--warning)}:host([data-kind=error]){flex-flow:row wrap;justify-content:flex-start;padding:var(--sp-3) var(--sp-4);margin-block-start:var(--sp-3);border-radius:var(--r-lg);background:var(--danger-bg);border:1px solid var(--danger-line);color:var(--ink);font-size:var(--text-base)}:host([data-kind=error]) .msg__icon{color:var(--danger)}.msg__retry{display:inline-flex;align-items:center;justify-content:center;gap:var(--sp-2);font-family:inherit;font-size:var(--text-base);font-weight:var(--fw-medium);border-radius:var(--r-md);border:1px solid transparent;cursor:pointer;transition:background var(--t-state),border-color var(--t-state),color var(--t-state),box-shadow var(--t-state),filter var(--t-state)}.msg__retry:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.msg__retry:disabled,.msg__retry[aria-disabled=true]{opacity:.5;cursor:not-allowed;pointer-events:none}.msg__retry{height:28px;padding-inline:var(--sp-3);font-size:var(--text-sm);background:var(--bg-2);border-color:var(--line);color:var(--ink-2)}.msg__retry:hover{background:var(--bg-3);border-color:var(--line-2);color:var(--ink)}\n"] }]
20672
+ `, styles: [":host{display:flex;flex-direction:column;align-items:center;gap:var(--sp-3);padding:48px;color:var(--ink-3);font-size:var(--text-lg)}.msg__icon{font-size:var(--text-lg)}.msg__text{margin:0}:host([data-kind=empty]){padding:56px 24px;gap:11px}:host([data-kind=empty]) .msg__icon{display:grid;place-items:center;inline-size:46px;block-size:46px;border-radius:16px;background:var(--fill-tertiary);border:1px solid var(--surface-border);color:var(--label-secondary);font-size:21px}:host([data-kind=empty]) .msg__text{font:600 14px/1.3 var(--font-sans);color:var(--label-primary)}.msg__note{margin:0;max-inline-size:320px;font:400 12.5px/1.5 var(--font-sans);color:var(--label-secondary);text-wrap:pretty}:host([data-kind=success]) .msg__icon{color:var(--success)}:host([data-kind=warning]) .msg__icon{color:var(--warning)}:host([data-kind=error]){flex-flow:row wrap;justify-content:flex-start;padding:var(--sp-3) var(--sp-4);margin-block-start:var(--sp-3);border-radius:var(--r-lg);background:var(--danger-bg);border:1px solid var(--danger-line);color:var(--ink);font-size:var(--text-base)}:host([data-kind=error]) .msg__icon{color:var(--danger)}.msg__retry{display:inline-flex;align-items:center;justify-content:center;gap:var(--sp-2);font-family:inherit;font-size:var(--text-base);font-weight:var(--fw-medium);border-radius:var(--r-md);border:1px solid transparent;cursor:pointer;transition:background var(--t-state),border-color var(--t-state),color var(--t-state),box-shadow var(--t-state),filter var(--t-state)}.msg__retry:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.msg__retry:disabled,.msg__retry[aria-disabled=true]{opacity:.5;cursor:not-allowed;pointer-events:none}.msg__retry{height:28px;padding-inline:var(--sp-3);font-size:var(--text-sm);background:var(--bg-2);border-color:var(--line);color:var(--ink-2)}.msg__retry:hover{background:var(--bg-3);border-color:var(--line-2);color:var(--ink)}\n"] }]
20321
20673
  }], propDecorators: { kind: [{ type: i0.Input, args: [{ isSignal: true, alias: "kind", required: false }] }], messageKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "messageKey", required: false }] }], iconClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconClass", required: false }] }], noteKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "noteKey", required: false }] }], retryLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "retryLabelKey", required: false }] }], showRetry: [{ type: i0.Input, args: [{ isSignal: true, alias: "showRetry", required: false }] }], retried: [{ type: i0.Output, args: ["retried"] }] } });
20322
20674
 
20323
20675
  /** Inline busy indicator — spinner glyph plus optional label. */
@@ -21783,8 +22135,22 @@ class FlyAppTopbarComponent {
21783
22135
  pendingFocus = signal(false, ...(ngDevMode ? [{ debugName: "pendingFocus" }] : /* istanbul ignore next */ []));
21784
22136
  flat = computed(() => flattenModules(this.sections()), ...(ngDevMode ? [{ debugName: "flat" }] : /* istanbul ignore next */ []));
21785
22137
  activeModule = computed(() => resolveActiveModule(this.sections(), this.activeKey()), ...(ngDevMode ? [{ debugName: "activeModule" }] : /* istanbul ignore next */ []));
21786
- brandAriaLabel = computed(() => this.i18n.t('ui.nav.home'), ...(ngDevMode ? [{ debugName: "brandAriaLabel" }] : /* istanbul ignore next */ []));
22138
+ // Both trigger names must START with the button's visible text ("Circles",
22139
+ // "Signals") — voice-control users activate controls by saying what they see,
22140
+ // so a name that omits it fails WCAG 2.5.3 Label in Name. The action context
22141
+ // is appended after the visible text; the popover menu keeps the plain
22142
+ // "Switch module" name since it renders no visible text of its own.
22143
+ brandAriaLabel = computed(() => {
22144
+ const home = this.i18n.t('ui.nav.home');
22145
+ const key = this.brandLabelKey();
22146
+ return key ? `${this.i18n.t(key)}, ${home}` : home;
22147
+ }, ...(ngDevMode ? [{ debugName: "brandAriaLabel" }] : /* istanbul ignore next */ []));
21787
22148
  switchAriaLabel = computed(() => this.i18n.t('ui.nav.switchModule'), ...(ngDevMode ? [{ debugName: "switchAriaLabel" }] : /* istanbul ignore next */ []));
22149
+ triggerAriaLabel = computed(() => {
22150
+ const active = this.activeModule();
22151
+ const visible = active ? this.i18n.t(active.labelKey) : this.i18n.t('ui.nav.selectModule');
22152
+ return `${visible}, ${this.i18n.t('ui.nav.switchModule')}`;
22153
+ }, ...(ngDevMode ? [{ debugName: "triggerAriaLabel" }] : /* istanbul ignore next */ []));
21788
22154
  constructor() {
21789
22155
  inject(DestroyRef).onDestroy(() => overlayStack.remove(this.stackHandle));
21790
22156
  effect(() => {
@@ -21890,11 +22256,11 @@ class FlyAppTopbarComponent {
21890
22256
  this.rows()[index]?.nativeElement.focus();
21891
22257
  }
21892
22258
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyAppTopbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
21893
- 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()" } }, 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]=\"switchAriaLabel()\"\r\n >\r\n @if (activeModule(); as active) {\r\n <span class=\"fly-topbar__module-icon\">\r\n @if (iconFor(active.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (active.iconClass) {\r\n <i [class]=\"active.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n <span class=\"fly-topbar__module-label\">{{ active.labelKey | translate }}</span>\r\n } @else {\r\n <span class=\"fly-topbar__module-label fly-topbar__module-label--empty\">\r\n {{ 'ui.nav.selectModule' | translate }}\r\n </span>\r\n }\r\n <svg class=\"fly-topbar__chevron\" width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\">\r\n <polyline points=\"6 9 12 15 18 9\" />\r\n </svg>\r\n </button>\r\n\r\n @if (open()) {\r\n <div class=\"fly-topbar__pop\" role=\"menu\" [attr.aria-label]=\"switchAriaLabel()\">\r\n <div class=\"fly-topbar__cols\">\r\n @for (section of sections(); track $index) {\r\n <div class=\"fly-topbar__col\">\r\n @if (section.titleKey; as titleKey) {\r\n <div class=\"fly-topbar__col-title\">{{ titleKey | translate }}</div>\r\n }\r\n @for (mod of section.modules; track mod.key) {\r\n <button\r\n #row\r\n type=\"button\"\r\n class=\"fly-topbar__item\"\r\n [class.fly-topbar__item--on]=\"mod.key === activeKey()\"\r\n role=\"menuitem\"\r\n [disabled]=\"mod.disabled ?? false\"\r\n [attr.tabindex]=\"indexOf(mod) === focusIndex() ? 0 : -1\"\r\n [attr.aria-current]=\"mod.key === activeKey() ? 'page' : null\"\r\n (click)=\"select(mod)\"\r\n (keydown)=\"onRowKeydown($event)\"\r\n >\r\n <span class=\"fly-topbar__item-icon\">\r\n @if (iconFor(mod.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (mod.iconClass) {\r\n <i [class]=\"mod.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n <span class=\"fly-topbar__item-main\">\r\n <span class=\"fly-topbar__item-label\">{{ mod.labelKey | translate }}</span>\r\n @if (mod.descKey; as descKey) {\r\n <span class=\"fly-topbar__item-desc\">{{ descKey | translate }}</span>\r\n }\r\n </span>\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n @if (footerLabel()) {\r\n <div class=\"fly-topbar__foot\">{{ footerLabel() }}</div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"fly-topbar__actions\"><ng-content select=\"[app-topbar-actions]\" /></div>\r\n</header>\r\n", styles: [":host{display:block;position:relative;z-index:var(--z-sticky)}.fly-topbar{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-3);padding:var(--sp-2) var(--sp-3);border-block-end:1px solid var(--line);background:var(--bg-3)}.fly-topbar__lead{display:flex;align-items:center;gap:var(--sp-2);min-inline-size:0}.fly-topbar__brand,.fly-topbar__module{display:inline-flex;align-items:center;gap:var(--sp-2);appearance:none;border:0;background:transparent;cursor:pointer;font:inherit;color:var(--ink);padding:var(--sp-1) var(--sp-2);border-radius:var(--r-md);transition:background var(--t-state)}.fly-topbar__brand:hover,.fly-topbar__module:hover{background:var(--bg-hover)}.fly-topbar__brand:focus-visible,.fly-topbar__module:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fly-topbar__brand-name{font-weight:var(--fw-semibold);white-space:nowrap}.fly-topbar__brand-mark{display:inline-flex;align-items:center;max-block-size:var(--icon-lg)}.fly-topbar__sep{color:var(--ink-4);-webkit-user-select:none;user-select:none}.fly-topbar__module{min-inline-size:0}.fly-topbar__module--on{background:var(--bg-hover)}.fly-topbar__module-label{font-weight:var(--fw-medium);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-topbar__module-label--empty{color:var(--ink-3)}.fly-topbar__module-icon,.fly-topbar__item-icon{display:inline-flex;align-items:center;justify-content:center;inline-size:var(--icon-md);block-size:var(--icon-md);flex:0 0 auto;color:var(--accent)}.fly-topbar__chevron{flex:0 0 auto;color:var(--ink-3);transition:transform var(--t-state)}.fly-topbar__module--on .fly-topbar__chevron{transform:rotate(180deg)}.fly-topbar__anchor{position:relative}.fly-topbar__pop{position:absolute;inset-block-start:calc(100% + var(--sp-1));inset-inline-start:0;z-index:var(--z-overlay);min-inline-size:260px;max-inline-size:min(680px,92vw);padding:var(--sp-3);border:1px solid var(--line);border-radius:var(--r-lg);background:var(--bg-2);box-shadow:var(--shadow-panel)}.fly-topbar__cols{display:flex;gap:var(--sp-4);align-items:flex-start}@media(width<=720px){.fly-topbar__cols{flex-direction:column;gap:var(--sp-3)}}.fly-topbar__col{display:flex;flex-direction:column;gap:2px;min-inline-size:220px;flex:1 1 0}.fly-topbar__col-title{font-size:var(--text-2xs);font-weight:var(--fw-semibold);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-3);padding:var(--sp-2) var(--sp-2) var(--sp-1)}.fly-topbar__item{display:flex;align-items:flex-start;gap:var(--sp-2);inline-size:100%;text-align:start;appearance:none;border:0;background:transparent;cursor:pointer;font:inherit;color:var(--ink);padding:var(--sp-2);border-radius:var(--r-md);transition:background var(--t-state)}.fly-topbar__item:hover:not(:disabled){background:var(--bg-hover)}.fly-topbar__item:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-2px}.fly-topbar__item:disabled{opacity:.5;cursor:default}.fly-topbar__item--on{background:var(--accent-soft);color:var(--on-accent-soft);box-shadow:inset 2px 0 0 0 var(--accent)}.fly-topbar__item-main{display:flex;flex-direction:column;gap:1px;min-inline-size:0}.fly-topbar__item-label{font-weight:var(--fw-medium);line-height:1.3}.fly-topbar__item-desc{font-size:var(--text-xs);color:var(--ink-3);line-height:1.35}.fly-topbar__foot{margin-block-start:var(--sp-3);padding-block-start:var(--sp-2);border-block-start:1px solid var(--line-3);font-size:var(--text-2xs);color:var(--ink-4)}.fly-topbar__actions{display:flex;align-items:center;gap:var(--sp-2);flex:0 0 auto}@media(forced-colors:active){.fly-topbar__item--on{border-inline-start:2px solid CanvasText}}@media(prefers-reduced-motion:reduce){.fly-topbar__brand,.fly-topbar__module,.fly-topbar__item,.fly-topbar__chevron{transition:none}}\n"], dependencies: [{ kind: "directive", type: FlyClickOutsideDirective, selector: "[flyClickOutside]", inputs: ["flyClickOutsideEnabled"], outputs: ["flyClickOutside"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
22259
+ 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()" } }, 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(--line);background:var(--bg-3)}.fly-topbar__lead{display:flex;align-items:center;gap:var(--sp-2);min-inline-size:0}.fly-topbar__brand,.fly-topbar__module{display:inline-flex;align-items:center;gap:var(--sp-2);appearance:none;border:0;background:transparent;cursor:pointer;font:inherit;color:var(--ink);padding:var(--sp-1) var(--sp-2);border-radius:var(--r-md);transition:background var(--t-state)}.fly-topbar__brand:hover,.fly-topbar__module:hover{background:var(--bg-hover)}.fly-topbar__brand:focus-visible,.fly-topbar__module:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fly-topbar__brand-name{font-weight:var(--fw-semibold);white-space:nowrap}.fly-topbar__brand-mark{display:inline-flex;align-items:center;max-block-size:var(--icon-lg)}.fly-topbar__sep{color:var(--ink-4);-webkit-user-select:none;user-select:none}.fly-topbar__module{min-inline-size:0}.fly-topbar__module--on{background:var(--bg-hover)}.fly-topbar__module-label{font-weight:var(--fw-medium);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-topbar__module-label--empty{color:var(--ink-3)}.fly-topbar__module-icon,.fly-topbar__item-icon{display:inline-flex;align-items:center;justify-content:center;inline-size:var(--icon-md);block-size:var(--icon-md);flex:0 0 auto;color:var(--accent)}.fly-topbar__chevron{flex:0 0 auto;color:var(--ink-3);transition:transform var(--t-state)}.fly-topbar__module--on .fly-topbar__chevron{transform:rotate(180deg)}.fly-topbar__anchor{position:relative}.fly-topbar__pop{position:absolute;inset-block-start:calc(100% + var(--sp-1));inset-inline-start:0;z-index:var(--z-overlay);min-inline-size:260px;max-inline-size:min(680px,92vw);padding:var(--sp-3);border:1px solid var(--line);border-radius:var(--r-lg);background:var(--bg-2);box-shadow:var(--shadow-panel)}.fly-topbar__cols{display:flex;gap:var(--sp-4);align-items:flex-start}@media(width<=720px){.fly-topbar__cols{flex-direction:column;gap:var(--sp-3)}}.fly-topbar__col{display:flex;flex-direction:column;gap:2px;min-inline-size:220px;flex:1 1 0}.fly-topbar__col-title{font-size:var(--text-2xs);font-weight:var(--fw-semibold);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-3);padding:var(--sp-2) var(--sp-2) var(--sp-1)}.fly-topbar__item{display:flex;align-items:flex-start;gap:var(--sp-2);inline-size:100%;text-align:start;appearance:none;border:0;background:transparent;cursor:pointer;font:inherit;color:var(--ink);padding:var(--sp-2);border-radius:var(--r-md);transition:background var(--t-state)}.fly-topbar__item:hover:not(:disabled){background:var(--bg-hover)}.fly-topbar__item:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-2px}.fly-topbar__item:disabled{opacity:.5;cursor:default}.fly-topbar__item--on{background:var(--accent-soft);color:var(--on-accent-soft);box-shadow:inset 2px 0 0 0 var(--accent)}.fly-topbar__item-main{display:flex;flex-direction:column;gap:1px;min-inline-size:0}.fly-topbar__item-label{font-weight:var(--fw-medium);line-height:1.3}.fly-topbar__item-desc{font-size:var(--text-xs);color:var(--ink-3);line-height:1.35}.fly-topbar__foot{margin-block-start:var(--sp-3);padding-block-start:var(--sp-2);border-block-start:1px solid var(--line-3);font-size:var(--text-2xs);color:var(--ink-4)}.fly-topbar__actions{display:flex;align-items:center;gap:var(--sp-2);flex:0 0 auto}@media(forced-colors:active){.fly-topbar__item--on{border-inline-start:2px solid CanvasText}}@media(prefers-reduced-motion:reduce){.fly-topbar__brand,.fly-topbar__module,.fly-topbar__item,.fly-topbar__chevron{transition:none}}\n"], dependencies: [{ kind: "directive", type: FlyClickOutsideDirective, selector: "[flyClickOutside]", inputs: ["flyClickOutsideEnabled"], outputs: ["flyClickOutside"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
21894
22260
  }
21895
22261
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyAppTopbarComponent, decorators: [{
21896
22262
  type: Component,
21897
- args: [{ selector: 'fly-app-topbar', standalone: true, imports: [TranslatePipe, FlyClickOutsideDirective, NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, host: { '(document:keydown.escape)': 'onEscape()' }, template: "<header class=\"fly-topbar\">\r\n <div class=\"fly-topbar__lead\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-topbar__brand\"\r\n (click)=\"brandSelected.emit()\"\r\n [attr.aria-label]=\"brandAriaLabel()\"\r\n >\r\n <span class=\"fly-topbar__brand-mark\"><ng-content select=\"[app-topbar-brand]\" /></span>\r\n @if (brandLabelKey(); as key) {\r\n <span class=\"fly-topbar__brand-name\">{{ key | translate }}</span>\r\n }\r\n </button>\r\n\r\n @if (activeModule()) {\r\n <span class=\"fly-topbar__sep\" aria-hidden=\"true\">/</span>\r\n }\r\n\r\n <div class=\"fly-topbar__anchor\" (flyClickOutside)=\"close()\" [flyClickOutsideEnabled]=\"open()\">\r\n <button\r\n #trigger\r\n type=\"button\"\r\n class=\"fly-topbar__module\"\r\n [class.fly-topbar__module--on]=\"open()\"\r\n (click)=\"toggle()\"\r\n (keydown)=\"onTriggerKeydown($event)\"\r\n aria-haspopup=\"menu\"\r\n [attr.aria-expanded]=\"open()\"\r\n [attr.aria-label]=\"switchAriaLabel()\"\r\n >\r\n @if (activeModule(); as active) {\r\n <span class=\"fly-topbar__module-icon\">\r\n @if (iconFor(active.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (active.iconClass) {\r\n <i [class]=\"active.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n <span class=\"fly-topbar__module-label\">{{ active.labelKey | translate }}</span>\r\n } @else {\r\n <span class=\"fly-topbar__module-label fly-topbar__module-label--empty\">\r\n {{ 'ui.nav.selectModule' | translate }}\r\n </span>\r\n }\r\n <svg class=\"fly-topbar__chevron\" width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\">\r\n <polyline points=\"6 9 12 15 18 9\" />\r\n </svg>\r\n </button>\r\n\r\n @if (open()) {\r\n <div class=\"fly-topbar__pop\" role=\"menu\" [attr.aria-label]=\"switchAriaLabel()\">\r\n <div class=\"fly-topbar__cols\">\r\n @for (section of sections(); track $index) {\r\n <div class=\"fly-topbar__col\">\r\n @if (section.titleKey; as titleKey) {\r\n <div class=\"fly-topbar__col-title\">{{ titleKey | translate }}</div>\r\n }\r\n @for (mod of section.modules; track mod.key) {\r\n <button\r\n #row\r\n type=\"button\"\r\n class=\"fly-topbar__item\"\r\n [class.fly-topbar__item--on]=\"mod.key === activeKey()\"\r\n role=\"menuitem\"\r\n [disabled]=\"mod.disabled ?? false\"\r\n [attr.tabindex]=\"indexOf(mod) === focusIndex() ? 0 : -1\"\r\n [attr.aria-current]=\"mod.key === activeKey() ? 'page' : null\"\r\n (click)=\"select(mod)\"\r\n (keydown)=\"onRowKeydown($event)\"\r\n >\r\n <span class=\"fly-topbar__item-icon\">\r\n @if (iconFor(mod.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (mod.iconClass) {\r\n <i [class]=\"mod.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n <span class=\"fly-topbar__item-main\">\r\n <span class=\"fly-topbar__item-label\">{{ mod.labelKey | translate }}</span>\r\n @if (mod.descKey; as descKey) {\r\n <span class=\"fly-topbar__item-desc\">{{ descKey | translate }}</span>\r\n }\r\n </span>\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n @if (footerLabel()) {\r\n <div class=\"fly-topbar__foot\">{{ footerLabel() }}</div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"fly-topbar__actions\"><ng-content select=\"[app-topbar-actions]\" /></div>\r\n</header>\r\n", styles: [":host{display:block;position:relative;z-index:var(--z-sticky)}.fly-topbar{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-3);padding:var(--sp-2) var(--sp-3);border-block-end:1px solid var(--line);background:var(--bg-3)}.fly-topbar__lead{display:flex;align-items:center;gap:var(--sp-2);min-inline-size:0}.fly-topbar__brand,.fly-topbar__module{display:inline-flex;align-items:center;gap:var(--sp-2);appearance:none;border:0;background:transparent;cursor:pointer;font:inherit;color:var(--ink);padding:var(--sp-1) var(--sp-2);border-radius:var(--r-md);transition:background var(--t-state)}.fly-topbar__brand:hover,.fly-topbar__module:hover{background:var(--bg-hover)}.fly-topbar__brand:focus-visible,.fly-topbar__module:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fly-topbar__brand-name{font-weight:var(--fw-semibold);white-space:nowrap}.fly-topbar__brand-mark{display:inline-flex;align-items:center;max-block-size:var(--icon-lg)}.fly-topbar__sep{color:var(--ink-4);-webkit-user-select:none;user-select:none}.fly-topbar__module{min-inline-size:0}.fly-topbar__module--on{background:var(--bg-hover)}.fly-topbar__module-label{font-weight:var(--fw-medium);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-topbar__module-label--empty{color:var(--ink-3)}.fly-topbar__module-icon,.fly-topbar__item-icon{display:inline-flex;align-items:center;justify-content:center;inline-size:var(--icon-md);block-size:var(--icon-md);flex:0 0 auto;color:var(--accent)}.fly-topbar__chevron{flex:0 0 auto;color:var(--ink-3);transition:transform var(--t-state)}.fly-topbar__module--on .fly-topbar__chevron{transform:rotate(180deg)}.fly-topbar__anchor{position:relative}.fly-topbar__pop{position:absolute;inset-block-start:calc(100% + var(--sp-1));inset-inline-start:0;z-index:var(--z-overlay);min-inline-size:260px;max-inline-size:min(680px,92vw);padding:var(--sp-3);border:1px solid var(--line);border-radius:var(--r-lg);background:var(--bg-2);box-shadow:var(--shadow-panel)}.fly-topbar__cols{display:flex;gap:var(--sp-4);align-items:flex-start}@media(width<=720px){.fly-topbar__cols{flex-direction:column;gap:var(--sp-3)}}.fly-topbar__col{display:flex;flex-direction:column;gap:2px;min-inline-size:220px;flex:1 1 0}.fly-topbar__col-title{font-size:var(--text-2xs);font-weight:var(--fw-semibold);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-3);padding:var(--sp-2) var(--sp-2) var(--sp-1)}.fly-topbar__item{display:flex;align-items:flex-start;gap:var(--sp-2);inline-size:100%;text-align:start;appearance:none;border:0;background:transparent;cursor:pointer;font:inherit;color:var(--ink);padding:var(--sp-2);border-radius:var(--r-md);transition:background var(--t-state)}.fly-topbar__item:hover:not(:disabled){background:var(--bg-hover)}.fly-topbar__item:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-2px}.fly-topbar__item:disabled{opacity:.5;cursor:default}.fly-topbar__item--on{background:var(--accent-soft);color:var(--on-accent-soft);box-shadow:inset 2px 0 0 0 var(--accent)}.fly-topbar__item-main{display:flex;flex-direction:column;gap:1px;min-inline-size:0}.fly-topbar__item-label{font-weight:var(--fw-medium);line-height:1.3}.fly-topbar__item-desc{font-size:var(--text-xs);color:var(--ink-3);line-height:1.35}.fly-topbar__foot{margin-block-start:var(--sp-3);padding-block-start:var(--sp-2);border-block-start:1px solid var(--line-3);font-size:var(--text-2xs);color:var(--ink-4)}.fly-topbar__actions{display:flex;align-items:center;gap:var(--sp-2);flex:0 0 auto}@media(forced-colors:active){.fly-topbar__item--on{border-inline-start:2px solid CanvasText}}@media(prefers-reduced-motion:reduce){.fly-topbar__brand,.fly-topbar__module,.fly-topbar__item,.fly-topbar__chevron{transition:none}}\n"] }]
22263
+ args: [{ selector: 'fly-app-topbar', standalone: true, imports: [TranslatePipe, FlyClickOutsideDirective, NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, host: { '(document:keydown.escape)': 'onEscape()' }, template: "<header class=\"fly-topbar\">\r\n <div class=\"fly-topbar__lead\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-topbar__brand\"\r\n (click)=\"brandSelected.emit()\"\r\n [attr.aria-label]=\"brandAriaLabel()\"\r\n >\r\n <span class=\"fly-topbar__brand-mark\"><ng-content select=\"[app-topbar-brand]\" /></span>\r\n @if (brandLabelKey(); as key) {\r\n <span class=\"fly-topbar__brand-name\">{{ key | translate }}</span>\r\n }\r\n </button>\r\n\r\n @if (activeModule()) {\r\n <span class=\"fly-topbar__sep\" aria-hidden=\"true\">/</span>\r\n }\r\n\r\n <div class=\"fly-topbar__anchor\" (flyClickOutside)=\"close()\" [flyClickOutsideEnabled]=\"open()\">\r\n <button\r\n #trigger\r\n type=\"button\"\r\n class=\"fly-topbar__module\"\r\n [class.fly-topbar__module--on]=\"open()\"\r\n (click)=\"toggle()\"\r\n (keydown)=\"onTriggerKeydown($event)\"\r\n aria-haspopup=\"menu\"\r\n [attr.aria-expanded]=\"open()\"\r\n [attr.aria-label]=\"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(--line);background:var(--bg-3)}.fly-topbar__lead{display:flex;align-items:center;gap:var(--sp-2);min-inline-size:0}.fly-topbar__brand,.fly-topbar__module{display:inline-flex;align-items:center;gap:var(--sp-2);appearance:none;border:0;background:transparent;cursor:pointer;font:inherit;color:var(--ink);padding:var(--sp-1) var(--sp-2);border-radius:var(--r-md);transition:background var(--t-state)}.fly-topbar__brand:hover,.fly-topbar__module:hover{background:var(--bg-hover)}.fly-topbar__brand:focus-visible,.fly-topbar__module:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fly-topbar__brand-name{font-weight:var(--fw-semibold);white-space:nowrap}.fly-topbar__brand-mark{display:inline-flex;align-items:center;max-block-size:var(--icon-lg)}.fly-topbar__sep{color:var(--ink-4);-webkit-user-select:none;user-select:none}.fly-topbar__module{min-inline-size:0}.fly-topbar__module--on{background:var(--bg-hover)}.fly-topbar__module-label{font-weight:var(--fw-medium);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-topbar__module-label--empty{color:var(--ink-3)}.fly-topbar__module-icon,.fly-topbar__item-icon{display:inline-flex;align-items:center;justify-content:center;inline-size:var(--icon-md);block-size:var(--icon-md);flex:0 0 auto;color:var(--accent)}.fly-topbar__chevron{flex:0 0 auto;color:var(--ink-3);transition:transform var(--t-state)}.fly-topbar__module--on .fly-topbar__chevron{transform:rotate(180deg)}.fly-topbar__anchor{position:relative}.fly-topbar__pop{position:absolute;inset-block-start:calc(100% + var(--sp-1));inset-inline-start:0;z-index:var(--z-overlay);min-inline-size:260px;max-inline-size:min(680px,92vw);padding:var(--sp-3);border:1px solid var(--line);border-radius:var(--r-lg);background:var(--bg-2);box-shadow:var(--shadow-panel)}.fly-topbar__cols{display:flex;gap:var(--sp-4);align-items:flex-start}@media(width<=720px){.fly-topbar__cols{flex-direction:column;gap:var(--sp-3)}}.fly-topbar__col{display:flex;flex-direction:column;gap:2px;min-inline-size:220px;flex:1 1 0}.fly-topbar__col-title{font-size:var(--text-2xs);font-weight:var(--fw-semibold);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-3);padding:var(--sp-2) var(--sp-2) var(--sp-1)}.fly-topbar__item{display:flex;align-items:flex-start;gap:var(--sp-2);inline-size:100%;text-align:start;appearance:none;border:0;background:transparent;cursor:pointer;font:inherit;color:var(--ink);padding:var(--sp-2);border-radius:var(--r-md);transition:background var(--t-state)}.fly-topbar__item:hover:not(:disabled){background:var(--bg-hover)}.fly-topbar__item:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-2px}.fly-topbar__item:disabled{opacity:.5;cursor:default}.fly-topbar__item--on{background:var(--accent-soft);color:var(--on-accent-soft);box-shadow:inset 2px 0 0 0 var(--accent)}.fly-topbar__item-main{display:flex;flex-direction:column;gap:1px;min-inline-size:0}.fly-topbar__item-label{font-weight:var(--fw-medium);line-height:1.3}.fly-topbar__item-desc{font-size:var(--text-xs);color:var(--ink-3);line-height:1.35}.fly-topbar__foot{margin-block-start:var(--sp-3);padding-block-start:var(--sp-2);border-block-start:1px solid var(--line-3);font-size:var(--text-2xs);color:var(--ink-4)}.fly-topbar__actions{display:flex;align-items:center;gap:var(--sp-2);flex:0 0 auto}@media(forced-colors:active){.fly-topbar__item--on{border-inline-start:2px solid CanvasText}}@media(prefers-reduced-motion:reduce){.fly-topbar__brand,.fly-topbar__module,.fly-topbar__item,.fly-topbar__chevron{transition:none}}\n"] }]
21898
22264
  }], 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 }] }] } });
21899
22265
 
21900
22266
  /**
@@ -23328,6 +23694,8 @@ function enterActivatesNatively(tagName, inputType = null) {
23328
23694
  * focus is trapped while open and returns to the prior element on close.
23329
23695
  */
23330
23696
  class FlyConfirmDialogComponent {
23697
+ /** Mobile chrome → the full-bleed confirm card. Read by the host class binding. */
23698
+ isMobile = inject(FLY_VIEWPORT_IS_MOBILE);
23331
23699
  open = input.required(...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
23332
23700
  kind = input('neutral', ...(ngDevMode ? [{ debugName: "kind" }] : /* istanbul ignore next */ []));
23333
23701
  titleKey = input.required(...(ngDevMode ? [{ debugName: "titleKey" }] : /* istanbul ignore next */ []));
@@ -23400,14 +23768,18 @@ class FlyConfirmDialogComponent {
23400
23768
  this.confirmed.emit();
23401
23769
  }
23402
23770
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyConfirmDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
23403
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyConfirmDialogComponent, isStandalone: true, selector: "fly-confirm-dialog", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: true, transformFunction: null }, kind: { classPropertyName: "kind", publicName: "kind", isSignal: true, isRequired: false, transformFunction: null }, titleKey: { classPropertyName: "titleKey", publicName: "titleKey", isSignal: true, isRequired: true, transformFunction: null }, messageKey: { classPropertyName: "messageKey", publicName: "messageKey", isSignal: true, isRequired: false, transformFunction: null }, confirmLabelKey: { classPropertyName: "confirmLabelKey", publicName: "confirmLabelKey", isSignal: true, isRequired: false, transformFunction: null }, cancelLabelKey: { classPropertyName: "cancelLabelKey", publicName: "cancelLabelKey", isSignal: true, isRequired: false, transformFunction: null }, requireText: { classPropertyName: "requireText", publicName: "requireText", isSignal: true, isRequired: false, transformFunction: null }, busy: { classPropertyName: "busy", publicName: "busy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { confirmed: "confirmed", cancelled: "cancelled" }, host: { listeners: { "document:keydown.escape": "onEscape()", "document:keydown.enter": "onEnter()" } }, ngImport: i0, template: "@if (open()) {\r\n <div class=\"cf\" role=\"presentation\">\r\n <div class=\"cf__scrim\" role=\"presentation\" (click)=\"cancel()\"></div>\r\n <div\r\n class=\"cf__panel\"\r\n [attr.data-kind]=\"kind()\"\r\n role=\"alertdialog\"\r\n aria-modal=\"true\"\r\n cdkTrapFocus\r\n cdkTrapFocusAutoCapture\r\n [attr.aria-label]=\"titleKey() | translate\"\r\n >\r\n <div class=\"cf__head\">\r\n <div class=\"cf__icon\" [attr.data-kind]=\"kind()\" aria-hidden=\"true\">\r\n @switch (kind()) {\r\n @case ('danger') {\r\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\r\n <circle cx=\"12\" cy=\"12\" r=\"9\"/>\r\n <path d=\"M12 8v5M12 16h.01\"/>\r\n </svg>\r\n }\r\n @case ('warn') {\r\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\r\n <path d=\"M12 9v4M12 17h.01\"/>\r\n <path d=\"M10.3 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z\"/>\r\n </svg>\r\n }\r\n @default {\r\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\r\n <circle cx=\"12\" cy=\"12\" r=\"9\"/>\r\n <path d=\"M12 8v.01M12 11v5\"/>\r\n </svg>\r\n }\r\n }\r\n </div>\r\n <h3 class=\"cf__title\">{{ titleKey() | translate }}</h3>\r\n <button\r\n type=\"button\"\r\n class=\"cf__close\"\r\n (click)=\"cancel()\"\r\n [attr.aria-label]=\"'ui.action.close' | translate\"\r\n >\u00D7</button>\r\n </div>\r\n\r\n <div class=\"cf__body\">\r\n @if (messageKey(); as key) {\r\n <p class=\"cf__msg\">{{ key | translate }}</p>\r\n }\r\n <ng-content />\r\n\r\n @if (requireText(); as text) {\r\n <label class=\"cf__gate\">\r\n <span class=\"cf__gate-l\">{{ 'ui.confirm.typeToConfirm' | translate: { text: text } }}</span>\r\n <input\r\n class=\"cf__gate-i mono\"\r\n [value]=\"typed()\"\r\n (input)=\"typed.set($any($event.target).value)\"\r\n [placeholder]=\"text\"\r\n autocomplete=\"off\"\r\n />\r\n </label>\r\n }\r\n </div>\r\n\r\n <div class=\"cf__ft\">\r\n <button type=\"button\" fly-button (click)=\"cancel()\">\r\n {{ cancelLabelKey() | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n fly-button\r\n [variant]=\"kind() === 'danger' ? 'danger-fill' : 'primary'\"\r\n [loading]=\"busy()\"\r\n [disabled]=\"!allowConfirm()\"\r\n (click)=\"confirm()\"\r\n >\r\n {{ confirmLabelKey() | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n}\r\n", styles: ["@charset \"UTF-8\";.cf{position:fixed;inset:0;z-index:var(--z-dialog);display:grid;place-items:center;padding:var(--sp-5)}.cf__scrim{position:absolute;inset:0;background:var(--scrim);-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);animation:cf-scrim-in var(--t-overlay)}@keyframes cf-scrim-in{0%{background:transparent;-webkit-backdrop-filter:blur(0);backdrop-filter:blur(0)}}.cf__panel{position:relative;width:min(480px,100%);border-radius:var(--r-lg);display:flex;flex-direction:column;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(var(--mat-panel),var(--mat-panel));box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset);-webkit-backdrop-filter:var(--glass2-blur);backdrop-filter:var(--glass2-blur)}@media(prefers-reduced-transparency:reduce){.cf__panel{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.cf__panel:before,.cf__panel:after{display:none}}@media(prefers-contrast:more){.cf__panel{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.cf__panel:after{animation:none}}.cf__panel{animation:menuIn .24s var(--nova-ease-structural) both}.cf__panel[data-kind=danger]{border-color:var(--danger-line)}.cf__panel[data-kind=warn]{border-color:var(--warning)}.cf__head{display:flex;align-items:flex-start;gap:14px;padding:20px 20px 14px}.cf__icon{width:36px;height:36px;border-radius:10px;display:grid;place-items:center;flex-shrink:0;background:var(--bg-3);color:var(--ink-2);border:1px solid var(--line)}.cf__icon[data-kind=warn]{background:var(--warning-bg);color:var(--warning-fg);border-color:var(--warning)}.cf__icon[data-kind=danger]{background:var(--danger-bg);color:var(--danger);border-color:var(--danger-line)}.cf__title{flex:1;min-width:0;font-size:var(--text-lg);font-weight:var(--fw-semibold);letter-spacing:-.005em;margin:0;padding-block-start:6px;color:var(--ink);line-height:1.35}.cf__close{width:28px;height:28px;border:0;background:transparent;color:var(--ink-3);cursor:pointer;border-radius:var(--r-sm);font-size:20px;line-height:1;display:grid;place-items:center;flex-shrink:0;margin-block-start:-4px;margin-inline-end:-4px;font-family:inherit;transition:background var(--t-state),color var(--t-state)}.cf__close:hover{color:var(--ink);background:var(--bg-hover)}.cf__close:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.cf__body{padding:0 20px 18px;display:flex;flex-direction:column;gap:14px}.cf__msg{font-size:var(--text-md);line-height:1.55;color:var(--ink-2);margin:0}.cf__gate{display:flex;flex-direction:column;gap:6px;padding-block-start:var(--sp-1)}.cf__gate-l{font-size:var(--text-2xs);color:var(--ink-2)}.cf__gate-i{width:100%;box-sizing:border-box;padding:var(--sp-2) var(--sp-3);background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-md);color:var(--ink);font-family:inherit;font-size:var(--text-base);transition:border-color var(--t-state),background var(--t-state)}.cf__gate-i::placeholder{color:var(--ink-4)}.cf__gate-i:hover{border-color:var(--line-2)}.cf__gate-i:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-1px;border-color:transparent}.cf__gate-i:disabled{opacity:.5;cursor:not-allowed;background:var(--bg-3)}.cf__gate-i{height:32px;padding-block:0;font-size:var(--text-sm)}.cf__gate-i.mono{font-family:var(--font-mono)}.cf__ft{display:flex;justify-content:flex-end;gap:var(--sp-2);padding:12px 16px;border-block-start:1px solid var(--w08);background:var(--w03);border-end-start-radius:var(--r-lg);border-end-end-radius:var(--r-lg)}@media(width<=480px){.cf__head{padding:16px 16px 12px}.cf__body{padding:0 16px 14px}.cf__ft{padding:10px 12px;flex-wrap:wrap}.cf__ft [fly-button]{flex:1 1 auto;justify-content:center}}\n"], dependencies: [{ kind: "directive", type: CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: FlyButtonComponent, selector: "button[fly-button], a[fly-button]", inputs: ["variant", "size", "loading"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
23771
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyConfirmDialogComponent, isStandalone: true, selector: "fly-confirm-dialog", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: true, transformFunction: null }, kind: { classPropertyName: "kind", publicName: "kind", isSignal: true, isRequired: false, transformFunction: null }, titleKey: { classPropertyName: "titleKey", publicName: "titleKey", isSignal: true, isRequired: true, transformFunction: null }, messageKey: { classPropertyName: "messageKey", publicName: "messageKey", isSignal: true, isRequired: false, transformFunction: null }, confirmLabelKey: { classPropertyName: "confirmLabelKey", publicName: "confirmLabelKey", isSignal: true, isRequired: false, transformFunction: null }, cancelLabelKey: { classPropertyName: "cancelLabelKey", publicName: "cancelLabelKey", isSignal: true, isRequired: false, transformFunction: null }, requireText: { classPropertyName: "requireText", publicName: "requireText", isSignal: true, isRequired: false, transformFunction: null }, busy: { classPropertyName: "busy", publicName: "busy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { confirmed: "confirmed", cancelled: "cancelled" }, host: { listeners: { "document:keydown.escape": "onEscape()", "document:keydown.enter": "onEnter()" }, properties: { "class.fly-confirm-dialog--mobile": "isMobile()" } }, ngImport: i0, template: "@if (open()) {\r\n <div class=\"cf\" role=\"presentation\">\r\n <div class=\"cf__scrim\" role=\"presentation\" (click)=\"cancel()\"></div>\r\n <div\r\n class=\"cf__panel\"\r\n [attr.data-kind]=\"kind()\"\r\n role=\"alertdialog\"\r\n aria-modal=\"true\"\r\n cdkTrapFocus\r\n cdkTrapFocusAutoCapture\r\n [attr.aria-label]=\"titleKey() | translate\"\r\n >\r\n <div class=\"cf__head\">\r\n <div class=\"cf__icon\" [attr.data-kind]=\"kind()\" aria-hidden=\"true\">\r\n @switch (kind()) {\r\n @case ('danger') {\r\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\r\n <circle cx=\"12\" cy=\"12\" r=\"9\"/>\r\n <path d=\"M12 8v5M12 16h.01\"/>\r\n </svg>\r\n }\r\n @case ('warn') {\r\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\r\n <path d=\"M12 9v4M12 17h.01\"/>\r\n <path d=\"M10.3 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z\"/>\r\n </svg>\r\n }\r\n @default {\r\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\r\n <circle cx=\"12\" cy=\"12\" r=\"9\"/>\r\n <path d=\"M12 8v.01M12 11v5\"/>\r\n </svg>\r\n }\r\n }\r\n </div>\r\n <h3 class=\"cf__title\">{{ titleKey() | translate }}</h3>\r\n <button\r\n type=\"button\"\r\n class=\"cf__close\"\r\n (click)=\"cancel()\"\r\n [attr.aria-label]=\"'ui.action.close' | translate\"\r\n >\u00D7</button>\r\n </div>\r\n\r\n <div class=\"cf__body\">\r\n @if (messageKey(); as key) {\r\n <p class=\"cf__msg\">{{ key | translate }}</p>\r\n }\r\n <ng-content />\r\n\r\n @if (requireText(); as text) {\r\n <label class=\"cf__gate\">\r\n <span class=\"cf__gate-l\">{{ 'ui.confirm.typeToConfirm' | translate: { text: text } }}</span>\r\n <input\r\n class=\"cf__gate-i mono\"\r\n [value]=\"typed()\"\r\n (input)=\"typed.set($any($event.target).value)\"\r\n [placeholder]=\"text\"\r\n autocomplete=\"off\"\r\n />\r\n </label>\r\n }\r\n </div>\r\n\r\n <div class=\"cf__ft\">\r\n <button type=\"button\" fly-button (click)=\"cancel()\">\r\n {{ cancelLabelKey() | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n fly-button\r\n [variant]=\"kind() === 'danger' ? 'danger-fill' : 'primary'\"\r\n [loading]=\"busy()\"\r\n [disabled]=\"!allowConfirm()\"\r\n (click)=\"confirm()\"\r\n >\r\n {{ confirmLabelKey() | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n}\r\n", styles: ["@charset \"UTF-8\";.cf{position:fixed;inset:0;z-index:var(--z-dialog);display:grid;place-items:center;padding:var(--sp-5)}.cf__scrim{position:absolute;inset:0;background:var(--scrim);-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);animation:cf-scrim-in var(--t-overlay)}@keyframes cf-scrim-in{0%{background:transparent;-webkit-backdrop-filter:blur(0);backdrop-filter:blur(0)}}.cf__panel{position:relative;width:min(480px,100%);border-radius:var(--r-lg);display:flex;flex-direction:column;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(var(--mat-panel),var(--mat-panel));box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset);-webkit-backdrop-filter:var(--glass2-blur);backdrop-filter:var(--glass2-blur)}@media(prefers-reduced-transparency:reduce){.cf__panel{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.cf__panel:before,.cf__panel:after{display:none}}@media(prefers-contrast:more){.cf__panel{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.cf__panel:after{animation:none}}.cf__panel{animation:menuIn .24s var(--nova-ease-structural) both}.cf__panel[data-kind=danger]{border-color:var(--danger-line)}.cf__panel[data-kind=warn]{border-color:var(--warning)}.cf__head{display:flex;align-items:flex-start;gap:14px;padding:20px 20px 14px}.cf__icon{width:36px;height:36px;border-radius:10px;display:grid;place-items:center;flex-shrink:0;background:var(--bg-3);color:var(--ink-2);border:1px solid var(--line)}.cf__icon[data-kind=warn]{background:var(--warning-bg);color:var(--warning-fg);border-color:var(--warning)}.cf__icon[data-kind=danger]{background:var(--danger-bg);color:var(--danger);border-color:var(--danger-line)}.cf__title{flex:1;min-width:0;font-size:var(--text-lg);font-weight:var(--fw-semibold);letter-spacing:-.005em;margin:0;padding-block-start:6px;color:var(--ink);line-height:1.35}.cf__close{width:28px;height:28px;border:0;background:transparent;color:var(--ink-3);cursor:pointer;border-radius:var(--r-sm);font-size:20px;line-height:1;display:grid;place-items:center;flex-shrink:0;margin-block-start:-4px;margin-inline-end:-4px;font-family:inherit;transition:background var(--t-state),color var(--t-state)}.cf__close:hover{color:var(--ink);background:var(--bg-hover)}.cf__close:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.cf__body{padding:0 20px 18px;display:flex;flex-direction:column;gap:14px}.cf__msg{font-size:var(--text-md);line-height:1.55;color:var(--ink-2);margin:0}.cf__gate{display:flex;flex-direction:column;gap:6px;padding-block-start:var(--sp-1)}.cf__gate-l{font-size:var(--text-2xs);color:var(--ink-2)}.cf__gate-i{width:100%;box-sizing:border-box;padding:var(--sp-2) var(--sp-3);background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-md);color:var(--ink);font-family:inherit;font-size:var(--text-base);transition:border-color var(--t-state),background var(--t-state)}.cf__gate-i::placeholder{color:var(--ink-4)}.cf__gate-i:hover{border-color:var(--line-2)}.cf__gate-i:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-1px;border-color:transparent}.cf__gate-i:disabled{opacity:.5;cursor:not-allowed;background:var(--bg-3)}.cf__gate-i{height:32px;padding-block:0;font-size:var(--text-sm)}.cf__gate-i.mono{font-family:var(--font-mono)}.cf__ft{display:flex;justify-content:flex-end;gap:var(--sp-2);padding:12px 16px;border-block-start:1px solid var(--w08);background:var(--w03);border-end-start-radius:var(--r-lg);border-end-end-radius:var(--r-lg)}@media(width<=480px){.cf__head{padding:16px 16px 12px}.cf__body{padding:0 16px 14px}.cf__ft{padding:10px 12px;flex-wrap:wrap}.cf__ft [fly-button]{flex:1 1 auto;justify-content:center}}:host(.fly-confirm-dialog--mobile) .cf{padding:16px}:host(.fly-confirm-dialog--mobile) .cf__panel{inline-size:100%;border-radius:18px}:host(.fly-confirm-dialog--mobile) .cf__ft{flex-wrap:nowrap;border-end-start-radius:18px;border-end-end-radius:18px}:host(.fly-confirm-dialog--mobile) .cf__ft [fly-button]{flex:1 1 0;min-inline-size:0;min-block-size:44px}\n"], dependencies: [{ kind: "directive", type: CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: FlyButtonComponent, selector: "button[fly-button], a[fly-button]", inputs: ["variant", "size", "loading"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
23404
23772
  }
23405
23773
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyConfirmDialogComponent, decorators: [{
23406
23774
  type: Component,
23407
23775
  args: [{ selector: 'fly-confirm-dialog', standalone: true, imports: [TranslatePipe, CdkTrapFocus, FlyButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: {
23408
23776
  '(document:keydown.escape)': 'onEscape()',
23409
23777
  '(document:keydown.enter)': 'onEnter()',
23410
- }, template: "@if (open()) {\r\n <div class=\"cf\" role=\"presentation\">\r\n <div class=\"cf__scrim\" role=\"presentation\" (click)=\"cancel()\"></div>\r\n <div\r\n class=\"cf__panel\"\r\n [attr.data-kind]=\"kind()\"\r\n role=\"alertdialog\"\r\n aria-modal=\"true\"\r\n cdkTrapFocus\r\n cdkTrapFocusAutoCapture\r\n [attr.aria-label]=\"titleKey() | translate\"\r\n >\r\n <div class=\"cf__head\">\r\n <div class=\"cf__icon\" [attr.data-kind]=\"kind()\" aria-hidden=\"true\">\r\n @switch (kind()) {\r\n @case ('danger') {\r\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\r\n <circle cx=\"12\" cy=\"12\" r=\"9\"/>\r\n <path d=\"M12 8v5M12 16h.01\"/>\r\n </svg>\r\n }\r\n @case ('warn') {\r\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\r\n <path d=\"M12 9v4M12 17h.01\"/>\r\n <path d=\"M10.3 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z\"/>\r\n </svg>\r\n }\r\n @default {\r\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\r\n <circle cx=\"12\" cy=\"12\" r=\"9\"/>\r\n <path d=\"M12 8v.01M12 11v5\"/>\r\n </svg>\r\n }\r\n }\r\n </div>\r\n <h3 class=\"cf__title\">{{ titleKey() | translate }}</h3>\r\n <button\r\n type=\"button\"\r\n class=\"cf__close\"\r\n (click)=\"cancel()\"\r\n [attr.aria-label]=\"'ui.action.close' | translate\"\r\n >\u00D7</button>\r\n </div>\r\n\r\n <div class=\"cf__body\">\r\n @if (messageKey(); as key) {\r\n <p class=\"cf__msg\">{{ key | translate }}</p>\r\n }\r\n <ng-content />\r\n\r\n @if (requireText(); as text) {\r\n <label class=\"cf__gate\">\r\n <span class=\"cf__gate-l\">{{ 'ui.confirm.typeToConfirm' | translate: { text: text } }}</span>\r\n <input\r\n class=\"cf__gate-i mono\"\r\n [value]=\"typed()\"\r\n (input)=\"typed.set($any($event.target).value)\"\r\n [placeholder]=\"text\"\r\n autocomplete=\"off\"\r\n />\r\n </label>\r\n }\r\n </div>\r\n\r\n <div class=\"cf__ft\">\r\n <button type=\"button\" fly-button (click)=\"cancel()\">\r\n {{ cancelLabelKey() | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n fly-button\r\n [variant]=\"kind() === 'danger' ? 'danger-fill' : 'primary'\"\r\n [loading]=\"busy()\"\r\n [disabled]=\"!allowConfirm()\"\r\n (click)=\"confirm()\"\r\n >\r\n {{ confirmLabelKey() | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n}\r\n", styles: ["@charset \"UTF-8\";.cf{position:fixed;inset:0;z-index:var(--z-dialog);display:grid;place-items:center;padding:var(--sp-5)}.cf__scrim{position:absolute;inset:0;background:var(--scrim);-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);animation:cf-scrim-in var(--t-overlay)}@keyframes cf-scrim-in{0%{background:transparent;-webkit-backdrop-filter:blur(0);backdrop-filter:blur(0)}}.cf__panel{position:relative;width:min(480px,100%);border-radius:var(--r-lg);display:flex;flex-direction:column;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(var(--mat-panel),var(--mat-panel));box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset);-webkit-backdrop-filter:var(--glass2-blur);backdrop-filter:var(--glass2-blur)}@media(prefers-reduced-transparency:reduce){.cf__panel{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.cf__panel:before,.cf__panel:after{display:none}}@media(prefers-contrast:more){.cf__panel{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.cf__panel:after{animation:none}}.cf__panel{animation:menuIn .24s var(--nova-ease-structural) both}.cf__panel[data-kind=danger]{border-color:var(--danger-line)}.cf__panel[data-kind=warn]{border-color:var(--warning)}.cf__head{display:flex;align-items:flex-start;gap:14px;padding:20px 20px 14px}.cf__icon{width:36px;height:36px;border-radius:10px;display:grid;place-items:center;flex-shrink:0;background:var(--bg-3);color:var(--ink-2);border:1px solid var(--line)}.cf__icon[data-kind=warn]{background:var(--warning-bg);color:var(--warning-fg);border-color:var(--warning)}.cf__icon[data-kind=danger]{background:var(--danger-bg);color:var(--danger);border-color:var(--danger-line)}.cf__title{flex:1;min-width:0;font-size:var(--text-lg);font-weight:var(--fw-semibold);letter-spacing:-.005em;margin:0;padding-block-start:6px;color:var(--ink);line-height:1.35}.cf__close{width:28px;height:28px;border:0;background:transparent;color:var(--ink-3);cursor:pointer;border-radius:var(--r-sm);font-size:20px;line-height:1;display:grid;place-items:center;flex-shrink:0;margin-block-start:-4px;margin-inline-end:-4px;font-family:inherit;transition:background var(--t-state),color var(--t-state)}.cf__close:hover{color:var(--ink);background:var(--bg-hover)}.cf__close:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.cf__body{padding:0 20px 18px;display:flex;flex-direction:column;gap:14px}.cf__msg{font-size:var(--text-md);line-height:1.55;color:var(--ink-2);margin:0}.cf__gate{display:flex;flex-direction:column;gap:6px;padding-block-start:var(--sp-1)}.cf__gate-l{font-size:var(--text-2xs);color:var(--ink-2)}.cf__gate-i{width:100%;box-sizing:border-box;padding:var(--sp-2) var(--sp-3);background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-md);color:var(--ink);font-family:inherit;font-size:var(--text-base);transition:border-color var(--t-state),background var(--t-state)}.cf__gate-i::placeholder{color:var(--ink-4)}.cf__gate-i:hover{border-color:var(--line-2)}.cf__gate-i:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-1px;border-color:transparent}.cf__gate-i:disabled{opacity:.5;cursor:not-allowed;background:var(--bg-3)}.cf__gate-i{height:32px;padding-block:0;font-size:var(--text-sm)}.cf__gate-i.mono{font-family:var(--font-mono)}.cf__ft{display:flex;justify-content:flex-end;gap:var(--sp-2);padding:12px 16px;border-block-start:1px solid var(--w08);background:var(--w03);border-end-start-radius:var(--r-lg);border-end-end-radius:var(--r-lg)}@media(width<=480px){.cf__head{padding:16px 16px 12px}.cf__body{padding:0 16px 14px}.cf__ft{padding:10px 12px;flex-wrap:wrap}.cf__ft [fly-button]{flex:1 1 auto;justify-content:center}}\n"] }]
23778
+ // S5.4's mobile confirm card. Same class-not-media-query mechanism, same
23779
+ // geometry, as `fly-message-box` — the two confirm surfaces of the platform
23780
+ // must not diverge on a phone just because one of them has no consumers yet.
23781
+ '[class.fly-confirm-dialog--mobile]': 'isMobile()',
23782
+ }, template: "@if (open()) {\r\n <div class=\"cf\" role=\"presentation\">\r\n <div class=\"cf__scrim\" role=\"presentation\" (click)=\"cancel()\"></div>\r\n <div\r\n class=\"cf__panel\"\r\n [attr.data-kind]=\"kind()\"\r\n role=\"alertdialog\"\r\n aria-modal=\"true\"\r\n cdkTrapFocus\r\n cdkTrapFocusAutoCapture\r\n [attr.aria-label]=\"titleKey() | translate\"\r\n >\r\n <div class=\"cf__head\">\r\n <div class=\"cf__icon\" [attr.data-kind]=\"kind()\" aria-hidden=\"true\">\r\n @switch (kind()) {\r\n @case ('danger') {\r\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\r\n <circle cx=\"12\" cy=\"12\" r=\"9\"/>\r\n <path d=\"M12 8v5M12 16h.01\"/>\r\n </svg>\r\n }\r\n @case ('warn') {\r\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\r\n <path d=\"M12 9v4M12 17h.01\"/>\r\n <path d=\"M10.3 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z\"/>\r\n </svg>\r\n }\r\n @default {\r\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\r\n <circle cx=\"12\" cy=\"12\" r=\"9\"/>\r\n <path d=\"M12 8v.01M12 11v5\"/>\r\n </svg>\r\n }\r\n }\r\n </div>\r\n <h3 class=\"cf__title\">{{ titleKey() | translate }}</h3>\r\n <button\r\n type=\"button\"\r\n class=\"cf__close\"\r\n (click)=\"cancel()\"\r\n [attr.aria-label]=\"'ui.action.close' | translate\"\r\n >\u00D7</button>\r\n </div>\r\n\r\n <div class=\"cf__body\">\r\n @if (messageKey(); as key) {\r\n <p class=\"cf__msg\">{{ key | translate }}</p>\r\n }\r\n <ng-content />\r\n\r\n @if (requireText(); as text) {\r\n <label class=\"cf__gate\">\r\n <span class=\"cf__gate-l\">{{ 'ui.confirm.typeToConfirm' | translate: { text: text } }}</span>\r\n <input\r\n class=\"cf__gate-i mono\"\r\n [value]=\"typed()\"\r\n (input)=\"typed.set($any($event.target).value)\"\r\n [placeholder]=\"text\"\r\n autocomplete=\"off\"\r\n />\r\n </label>\r\n }\r\n </div>\r\n\r\n <div class=\"cf__ft\">\r\n <button type=\"button\" fly-button (click)=\"cancel()\">\r\n {{ cancelLabelKey() | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n fly-button\r\n [variant]=\"kind() === 'danger' ? 'danger-fill' : 'primary'\"\r\n [loading]=\"busy()\"\r\n [disabled]=\"!allowConfirm()\"\r\n (click)=\"confirm()\"\r\n >\r\n {{ confirmLabelKey() | translate }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n}\r\n", styles: ["@charset \"UTF-8\";.cf{position:fixed;inset:0;z-index:var(--z-dialog);display:grid;place-items:center;padding:var(--sp-5)}.cf__scrim{position:absolute;inset:0;background:var(--scrim);-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);animation:cf-scrim-in var(--t-overlay)}@keyframes cf-scrim-in{0%{background:transparent;-webkit-backdrop-filter:blur(0);backdrop-filter:blur(0)}}.cf__panel{position:relative;width:min(480px,100%);border-radius:var(--r-lg);display:flex;flex-direction:column;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(var(--mat-panel),var(--mat-panel));box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset);-webkit-backdrop-filter:var(--glass2-blur);backdrop-filter:var(--glass2-blur)}@media(prefers-reduced-transparency:reduce){.cf__panel{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.cf__panel:before,.cf__panel:after{display:none}}@media(prefers-contrast:more){.cf__panel{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.cf__panel:after{animation:none}}.cf__panel{animation:menuIn .24s var(--nova-ease-structural) both}.cf__panel[data-kind=danger]{border-color:var(--danger-line)}.cf__panel[data-kind=warn]{border-color:var(--warning)}.cf__head{display:flex;align-items:flex-start;gap:14px;padding:20px 20px 14px}.cf__icon{width:36px;height:36px;border-radius:10px;display:grid;place-items:center;flex-shrink:0;background:var(--bg-3);color:var(--ink-2);border:1px solid var(--line)}.cf__icon[data-kind=warn]{background:var(--warning-bg);color:var(--warning-fg);border-color:var(--warning)}.cf__icon[data-kind=danger]{background:var(--danger-bg);color:var(--danger);border-color:var(--danger-line)}.cf__title{flex:1;min-width:0;font-size:var(--text-lg);font-weight:var(--fw-semibold);letter-spacing:-.005em;margin:0;padding-block-start:6px;color:var(--ink);line-height:1.35}.cf__close{width:28px;height:28px;border:0;background:transparent;color:var(--ink-3);cursor:pointer;border-radius:var(--r-sm);font-size:20px;line-height:1;display:grid;place-items:center;flex-shrink:0;margin-block-start:-4px;margin-inline-end:-4px;font-family:inherit;transition:background var(--t-state),color var(--t-state)}.cf__close:hover{color:var(--ink);background:var(--bg-hover)}.cf__close:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.cf__body{padding:0 20px 18px;display:flex;flex-direction:column;gap:14px}.cf__msg{font-size:var(--text-md);line-height:1.55;color:var(--ink-2);margin:0}.cf__gate{display:flex;flex-direction:column;gap:6px;padding-block-start:var(--sp-1)}.cf__gate-l{font-size:var(--text-2xs);color:var(--ink-2)}.cf__gate-i{width:100%;box-sizing:border-box;padding:var(--sp-2) var(--sp-3);background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-md);color:var(--ink);font-family:inherit;font-size:var(--text-base);transition:border-color var(--t-state),background var(--t-state)}.cf__gate-i::placeholder{color:var(--ink-4)}.cf__gate-i:hover{border-color:var(--line-2)}.cf__gate-i:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-1px;border-color:transparent}.cf__gate-i:disabled{opacity:.5;cursor:not-allowed;background:var(--bg-3)}.cf__gate-i{height:32px;padding-block:0;font-size:var(--text-sm)}.cf__gate-i.mono{font-family:var(--font-mono)}.cf__ft{display:flex;justify-content:flex-end;gap:var(--sp-2);padding:12px 16px;border-block-start:1px solid var(--w08);background:var(--w03);border-end-start-radius:var(--r-lg);border-end-end-radius:var(--r-lg)}@media(width<=480px){.cf__head{padding:16px 16px 12px}.cf__body{padding:0 16px 14px}.cf__ft{padding:10px 12px;flex-wrap:wrap}.cf__ft [fly-button]{flex:1 1 auto;justify-content:center}}:host(.fly-confirm-dialog--mobile) .cf{padding:16px}:host(.fly-confirm-dialog--mobile) .cf__panel{inline-size:100%;border-radius:18px}:host(.fly-confirm-dialog--mobile) .cf__ft{flex-wrap:nowrap;border-end-start-radius:18px;border-end-end-radius:18px}:host(.fly-confirm-dialog--mobile) .cf__ft [fly-button]{flex:1 1 0;min-inline-size:0;min-block-size:44px}\n"] }]
23411
23783
  }], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: true }] }], kind: [{ type: i0.Input, args: [{ isSignal: true, alias: "kind", required: false }] }], titleKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleKey", required: true }] }], messageKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "messageKey", required: false }] }], confirmLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "confirmLabelKey", required: false }] }], cancelLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelLabelKey", required: false }] }], requireText: [{ type: i0.Input, args: [{ isSignal: true, alias: "requireText", required: false }] }], busy: [{ type: i0.Input, args: [{ isSignal: true, alias: "busy", required: false }] }], confirmed: [{ type: i0.Output, args: ["confirmed"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }] } });
23412
23784
 
23413
23785
  /**
@@ -24369,5 +24741,5 @@ const AUDIENCE_ERROR_CODES = {
24369
24741
  * Generated bundle index. Do not edit.
24370
24742
  */
24371
24743
 
24372
- export { AGENT_DRAG_MIME, AGENT_PAYLOAD_VERSION, APP_LOOKUP, AUDIENCE_ERROR_CODES, AUDIENCE_LIMITS, AUDIENCE_PRESETS, AUDIENCE_TERM_KINDS, AgentActionBus, AgentActionUnsupportedDispatchError, AgentCommandRegistry, AgentDropRegistry, AgentFlightAnimator, AgentLookupRegistry, AgentPayloadOversizeError, AudienceBuilderComponent, AuthService, BilingualFieldComponent, CRON_MODES, CRON_WEEKDAYS, ContextMenuComponent, DEFAULT_AGENT_PAYLOAD_LIMITS, DEFAULT_FLY_THEME_MODE, DS_BASELINE_LOCALES, DashboardKpiComponent, DialogResult, EntityLookupComponent, FLYOS_LAUNCH_EVENT, FLYOS_LAUNCH_REQUEST_EVENT, FLYOS_REMOTE_ROUTE_EVENT, FLY_ACCENT_PROPERTY, FLY_ADMIN_ROLES, FLY_CHUNK_RELOAD_FLAG, FLY_COMPACT_THRESHOLD, FLY_COUNTRIES, FLY_EMOJI_BY_ID, FLY_EMOJI_CATEGORIES, FLY_EMOJI_CATEGORY_BY_ID, FLY_EMOJI_DEFAULT_CATEGORIES, FLY_EMOJI_PACK, FLY_EMOJI_PACK_MISSING_MESSAGE, FLY_EMOJI_PREVIEW_COUNT, FLY_EMPTY_VALUE, FLY_LOCALE_CATALOG, FLY_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_WINDOW_HELP_HINT_EVENT, FLY_WINDOW_PAGE_TITLE_EVENT, FlyAchievementProgressComponent, FlyActionMenuComponent, FlyActionStackComponent, FlyAgentDraggableDirective, FlyAnimatedEmojiComponent, FlyAppHomeComponent, FlyAppTopbarComponent, FlyAuthLiveRefresh, FlyBadgeWallComponent, FlyBlockUiComponent, FlyBreadcrumbComponent, FlyButtonComponent, FlyBytesPipe, FlyCaptchaComponent, FlyCardActionsComponent, FlyCardBodyComponent, FlyCardComponent, FlyCardFooterComponent, FlyCardGridComponent, FlyCardMediaComponent, FlyCardMetaComponent, FlyCardStatusComponent, FlyCardTitleComponent, FlyCellDirective, FlyCheckboxComponent, FlyChipComponent, FlyChunkReloadErrorHandler, FlyClickOutsideDirective, FlyCollapsibleComponent, FlyCommentThreadComponent, FlyCompactNumberPipe, FlyConfirmDialogComponent, FlyControlDirective, FlyCronBuilderComponent, FlyCurrencySelectorComponent, FlyDataTableComponent, FlyDatePipe, FlyDateTimePipe, FlyDebouncer, FlyDecimalNumberPipe, 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, FlyPaginationComponent, FlyPeoplePickerComponent, FlyPointsTierComponent, FlyProgressComponent, FlyRelativeTimePipe, FlyRemoteContextService, FlyRemoteRouter, FlyRemoteRouterOutletComponent, FlySearchInputComponent, FlySectionHeaderComponent, FlySecureSrcDirective, FlySegmentedComponent, FlySelectComponent, FlySkeletonComponent, FlySliderComponent, FlySparklineComponent, FlySpinnerComponent, FlyStandaloneAuthCallbackComponent, FlyStandaloneAuthService, FlyStateMessageComponent, FlyDynamicFormComponent as FlySurveyFormComponent, FlyTabComponent, FlyTabsComponent, FlyTagsInputComponent, FlyThemeService, FlyTimePipe, FlyToastService, FlyToggleComponent, FlyTooltipDirective, FlyTreeNavComponent, FlyTypeaheadComponent, FlyUserDirectoryService, FlyWindowHelpService, FlyWindowTitleService, FlyosPendingLaunchesGlobalKey, FlyosShellHandlesLaunchRequestsGlobalKey, FlyosShellOwnsHistoryGlobalKey, GANTT_ZOOMS, I18nService, LAUNCH_CONTEXT, 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, flyFormatTime, flyFullNumber, flyNextEnabledIndex, flyNextSegmentIndex, flyRelativeTime, flyResolveActiveTab, flyRoleGuard, flyScanPendingDelayMs, flyScanRetry, flySignedCompact, flySkinToneLabelKey, flyStandaloneAuthGuard, flyStandaloneAuthInterceptor, flyToDateOnly, flyToPage, flyUnwrap, 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, 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 };
24744
+ export { AGENT_DRAG_MIME, AGENT_PAYLOAD_VERSION, APP_LOOKUP, AUDIENCE_ERROR_CODES, AUDIENCE_LIMITS, AUDIENCE_PRESETS, AUDIENCE_TERM_KINDS, AgentActionBus, AgentActionUnsupportedDispatchError, AgentCommandRegistry, AgentDropRegistry, AgentFlightAnimator, AgentLookupRegistry, AgentPayloadOversizeError, AudienceBuilderComponent, AuthService, BilingualFieldComponent, CRON_MODES, CRON_WEEKDAYS, ContextMenuComponent, DEFAULT_AGENT_PAYLOAD_LIMITS, DEFAULT_FLY_THEME_MODE, DS_BASELINE_LOCALES, DashboardKpiComponent, DialogResult, EntityLookupComponent, FLYOS_LAUNCH_EVENT, FLYOS_LAUNCH_REQUEST_EVENT, FLYOS_REMOTE_ROUTE_EVENT, FLY_ACCENT_PROPERTY, FLY_ADMIN_ROLES, FLY_CHUNK_RELOAD_FLAG, FLY_COMPACT_THRESHOLD, FLY_COUNTRIES, FLY_EMOJI_BY_ID, FLY_EMOJI_CATEGORIES, FLY_EMOJI_CATEGORY_BY_ID, FLY_EMOJI_DEFAULT_CATEGORIES, FLY_EMOJI_PACK, FLY_EMOJI_PACK_MISSING_MESSAGE, FLY_EMOJI_PREVIEW_COUNT, FLY_EMPTY_VALUE, FLY_LOCALE_CATALOG, FLY_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, 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, FlyCurrencySelectorComponent, FlyDataTableComponent, FlyDatePipe, FlyDateTimePipe, FlyDebouncer, FlyDecimalNumberPipe, 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, FlyStateMessageComponent, FlyDynamicFormComponent as FlySurveyFormComponent, FlyTabComponent, FlyTabsComponent, FlyTagsInputComponent, FlyThemeService, FlyTimePipe, FlyToastService, FlyToggleComponent, FlyTooltipDirective, FlyTreeNavComponent, FlyTypeaheadComponent, FlyUserDirectoryService, FlyWindowHelpService, FlyWindowTitleService, FlyosPendingLaunchesGlobalKey, FlyosShellHandlesLaunchRequestsGlobalKey, FlyosShellOwnsHistoryGlobalKey, GANTT_ZOOMS, I18nService, LAUNCH_CONTEXT, 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, 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, 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 };
24373
24745
  //# sourceMappingURL=flyos-design-system.mjs.map