@flyos/design-system 3.2.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -47,7 +47,7 @@ import { CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
47
47
  // tools/publish-library.ps1 at bump time, and asserted by the spec beside this file.
48
48
  // Used only for the diagnostic message; the duplicate-instance detection itself is
49
49
  // version-agnostic, so a stale literal misnames a fork rather than hiding one.
50
- const FLY_DS_VERSION = '3.2.0';
50
+ const FLY_DS_VERSION = '3.3.0';
51
51
  const FLY_DS_REGISTRY_KEY = '__FLY_DS_INSTANCES__';
52
52
  /**
53
53
  * Records this design-system instance on the shared `scope` and returns the
@@ -6177,6 +6177,24 @@ class FlyClickOutsideDirective {
6177
6177
  * Defaults to always-on for an always-mounted consumer.
6178
6178
  */
6179
6179
  enabled = input(true, { ...(ngDevMode ? { debugName: "enabled" } : /* istanbul ignore next */ {}), alias: 'flyClickOutsideEnabled' });
6180
+ /**
6181
+ * Extra element(s) that count as INSIDE even though they are not in the host —
6182
+ * the escape hatch for a popover whose TRIGGER cannot be wrapped by the host.
6183
+ *
6184
+ * The docblock's canonical shape puts the host around trigger and panel both, and
6185
+ * that is still the right answer whenever it is available. It is not always: a
6186
+ * panel anchored to a control in the desktop shell's chrome (a magic-bar filter
6187
+ * toggle) has its trigger in another component, another stacking context and
6188
+ * another bundle. Left unlisted, the trigger reads as outside, and because this
6189
+ * directive fires on `pointerdown` — strictly BEFORE the trigger's own `click` —
6190
+ * pressing an open panel's own toggle dismisses it and the toggle then reopens it.
6191
+ * The panel appears not to close at all, which reads as this directive being
6192
+ * broken rather than as an ordering problem.
6193
+ *
6194
+ * A single element or an array; `null`/empty (the default) keeps the host-only
6195
+ * behaviour byte-for-byte.
6196
+ */
6197
+ ignore = input(null, { ...(ngDevMode ? { debugName: "ignore" } : /* istanbul ignore next */ {}), alias: 'flyClickOutsideIgnore' });
6180
6198
  /** Emitted once when a pointer press lands outside the host while enabled. */
6181
6199
  flyClickOutside = output();
6182
6200
  // `pointerdown` (not `click`): fires at the very start of the gesture — before focus
@@ -6192,14 +6210,18 @@ class FlyClickOutsideDirective {
6192
6210
  // even if the target detaches mid-handler (a menu item that removes its own row),
6193
6211
  // and it pierces shadow roots. Fall back to `contains` for any UA without it.
6194
6212
  const path = event.composedPath?.();
6195
- const inside = path && path.length
6196
- ? path.includes(host)
6197
- : host.contains(event.target);
6198
- if (!inside)
6199
- this.flyClickOutside.emit();
6213
+ const target = event.target;
6214
+ const within = (el) => !!el && (path && path.length ? path.includes(el) : el.contains(target));
6215
+ if (within(host))
6216
+ return;
6217
+ const ignore = this.ignore();
6218
+ const extras = ignore === null ? [] : Array.isArray(ignore) ? ignore : [ignore];
6219
+ if (extras.some(within))
6220
+ return;
6221
+ this.flyClickOutside.emit();
6200
6222
  }
6201
6223
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyClickOutsideDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
6202
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.19", type: FlyClickOutsideDirective, isStandalone: true, selector: "[flyClickOutside]", inputs: { enabled: { classPropertyName: "enabled", publicName: "flyClickOutsideEnabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { flyClickOutside: "flyClickOutside" }, host: { listeners: { "document:pointerdown": "onDocumentPointerDown($event)" } }, ngImport: i0 });
6224
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.19", type: FlyClickOutsideDirective, isStandalone: true, selector: "[flyClickOutside]", inputs: { enabled: { classPropertyName: "enabled", publicName: "flyClickOutsideEnabled", isSignal: true, isRequired: false, transformFunction: null }, ignore: { classPropertyName: "ignore", publicName: "flyClickOutsideIgnore", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { flyClickOutside: "flyClickOutside" }, host: { listeners: { "document:pointerdown": "onDocumentPointerDown($event)" } }, ngImport: i0 });
6203
6225
  }
6204
6226
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyClickOutsideDirective, decorators: [{
6205
6227
  type: Directive,
@@ -6207,7 +6229,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
6207
6229
  selector: '[flyClickOutside]',
6208
6230
  standalone: true,
6209
6231
  }]
6210
- }], propDecorators: { enabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "flyClickOutsideEnabled", required: false }] }], flyClickOutside: [{ type: i0.Output, args: ["flyClickOutside"] }], onDocumentPointerDown: [{
6232
+ }], propDecorators: { enabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "flyClickOutsideEnabled", required: false }] }], ignore: [{ type: i0.Input, args: [{ isSignal: true, alias: "flyClickOutsideIgnore", required: false }] }], flyClickOutside: [{ type: i0.Output, args: ["flyClickOutside"] }], onDocumentPointerDown: [{
6211
6233
  type: HostListener,
6212
6234
  args: ['document:pointerdown', ['$event']]
6213
6235
  }] } });
@@ -7857,7 +7879,7 @@ class FlyEmojiPickerComponent {
7857
7879
  this.selectionChange.emit(next);
7858
7880
  }
7859
7881
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyEmojiPickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7860
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyEmojiPickerComponent, isStandalone: true, selector: "fly-emoji-picker", inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, selectionMode: { classPropertyName: "selectionMode", publicName: "selectionMode", isSignal: true, isRequired: false, transformFunction: null }, selectedIds: { classPropertyName: "selectedIds", publicName: "selectedIds", isSignal: true, isRequired: false, transformFunction: null }, categories: { classPropertyName: "categories", publicName: "categories", isSignal: true, isRequired: false, transformFunction: null }, defaultCategory: { classPropertyName: "defaultCategory", publicName: "defaultCategory", isSignal: true, isRequired: false, transformFunction: null }, skinTones: { classPropertyName: "skinTones", publicName: "skinTones", isSignal: true, isRequired: false, transformFunction: null }, skinTone: { classPropertyName: "skinTone", publicName: "skinTone", isSignal: true, isRequired: false, transformFunction: null }, showMore: { classPropertyName: "showMore", publicName: "showMore", isSignal: true, isRequired: false, transformFunction: null }, previewCount: { classPropertyName: "previewCount", publicName: "previewCount", isSignal: true, isRequired: false, transformFunction: null }, emojiAnimation: { classPropertyName: "emojiAnimation", publicName: "emojiAnimation", isSignal: true, isRequired: false, transformFunction: null }, dismissOnOutsidePress: { classPropertyName: "dismissOnOutsidePress", publicName: "dismissOnOutsidePress", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { stickerPicked: "stickerPicked", toggled: "toggled", selectionChange: "selectionChange", skinToneChange: "skinToneChange", dismissed: "dismissed" }, host: { properties: { "class.fly-emoji-picker--small": "size() === 'small'", "class.fly-emoji-picker--medium": "size() === 'medium'", "class.fly-emoji-picker--large": "size() === 'large'" } }, viewQueries: [{ propertyName: "searchRef", first: true, predicate: ["searchRef"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\r\n class=\"fly-emoji-picker\"\r\n [flyClickOutsideEnabled]=\"dismissOnOutsidePress()\"\r\n (flyClickOutside)=\"dismissed.emit()\"\r\n [attr.aria-label]=\"'common.label.emoji_picker' | translate\">\r\n <!-- Stickers mode only: the emoji grid is unchanged, stickers are an ADDITIONAL surface. -->\r\n @if (showTabs()) {\r\n <div\r\n class=\"fly-emoji-picker__tabs\"\r\n role=\"tablist\"\r\n [attr.aria-label]=\"'common.label.emoji_tabs' | translate\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__tab\"\r\n role=\"tab\"\r\n id=\"fly-emoji-tab-emoji\"\r\n aria-controls=\"fly-emoji-panel-emoji\"\r\n [class.is-active]=\"activeTab() === 'emoji'\"\r\n [attr.aria-selected]=\"activeTab() === 'emoji'\"\r\n [attr.tabindex]=\"activeTab() === 'emoji' ? 0 : -1\"\r\n (keydown)=\"onTabKeydown($event)\"\r\n (click)=\"selectTab('emoji')\">\r\n {{ 'common.label.emoji_tab_emoji' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__tab\"\r\n role=\"tab\"\r\n id=\"fly-emoji-tab-stickers\"\r\n aria-controls=\"fly-emoji-panel-stickers\"\r\n [class.is-active]=\"activeTab() === 'stickers'\"\r\n [attr.aria-selected]=\"activeTab() === 'stickers'\"\r\n [attr.tabindex]=\"activeTab() === 'stickers' ? 0 : -1\"\r\n (keydown)=\"onTabKeydown($event)\"\r\n (click)=\"selectTab('stickers')\">\r\n {{ 'common.label.emoji_tab_stickers' | translate }}\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (showTabs() && activeTab() === 'stickers') {\r\n <div\r\n class=\"fly-emoji-picker__stickers\"\r\n id=\"fly-emoji-panel-stickers\"\r\n role=\"tabpanel\"\r\n aria-labelledby=\"fly-emoji-tab-stickers\">\r\n @for (entry of stickers(); track entry.id) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__sticker\"\r\n [flyTooltip]=\"entry.label\"\r\n [attr.aria-label]=\"entry.label\"\r\n (pointerenter)=\"onCellHover(sticker)\"\r\n (pointerleave)=\"cancelHover()\"\r\n (click)=\"pickSticker(entry)\">\r\n <fly-animated-emoji #sticker [id]=\"entry.id\" [size]=\"64\" />\r\n </button>\r\n } @empty {\r\n <p class=\"fly-emoji-picker__empty\">{{ 'agent.lookup.no_results' | translate }}</p>\r\n }\r\n </div>\r\n }\r\n\r\n @if (!showTabs() || activeTab() === 'emoji') {\r\n <!-- Search + tone sit together as one chrome bar: the tone swatches are hands, and on their own row\r\n above the grid they read as pickable content rather than a setting. -->\r\n <div class=\"fly-emoji-picker__bar\">\r\n <input\r\n class=\"fly-emoji-picker__search\"\r\n type=\"text\"\r\n #searchRef\r\n [value]=\"query()\"\r\n (input)=\"onQueryInput(searchRef.value)\"\r\n [placeholder]=\"'common.label.search_emoji' | translate\"\r\n [attr.aria-label]=\"'common.label.search_emoji' | translate\"\r\n spellcheck=\"false\"\r\n autocomplete=\"off\" />\r\n\r\n @if (showToneStrip()) {\r\n <!-- Escape-to-close listener only \u2014 every actual control inside (trigger + options) is its\r\n own focusable button, so this wrapper itself doesn't need to be. -->\r\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\r\n <div\r\n class=\"fly-emoji-picker__tones\"\r\n [flyClickOutsideEnabled]=\"toneMenuOpen()\"\r\n (flyClickOutside)=\"toneMenuOpen.set(false)\"\r\n (keydown.escape)=\"toneMenuOpen.set(false)\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__tone-trigger\"\r\n aria-haspopup=\"listbox\"\r\n [attr.aria-expanded]=\"toneMenuOpen()\"\r\n [attr.aria-label]=\"'common.label.skin_tone' | translate\"\r\n [flyTooltip]=\"toneLabelKey(effectiveTone()) | translate\"\r\n (click)=\"toneMenuOpen.set(!toneMenuOpen())\">\r\n <span class=\"fly-emoji-picker__tone-glyph\">{{ toneSwatch(effectiveTone()) }}</span>\r\n <i class=\"pi pi-chevron-down fly-emoji-picker__tone-caret\" aria-hidden=\"true\"></i>\r\n </button>\r\n\r\n @if (toneMenuOpen()) {\r\n <div\r\n class=\"fly-emoji-picker__tone-menu\"\r\n role=\"listbox\"\r\n [attr.aria-label]=\"'common.label.skin_tone' | translate\">\r\n @for (tone of tones; track tone) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__tone-option\"\r\n role=\"option\"\r\n [class.is-active]=\"effectiveTone() === tone\"\r\n [attr.aria-selected]=\"effectiveTone() === tone\"\r\n [flyTooltip]=\"toneLabelKey(tone) | translate\"\r\n (click)=\"onToneOptionClick(tone)\">\r\n {{ toneSwatch(tone) }}\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- ONE scrolling list; each category is a separator label over its own grid. Tabs wrapped to three\r\n rows in a sidebar drawer and cost a click to reach any group. -->\r\n <div\r\n class=\"fly-emoji-picker__list\"\r\n role=\"listbox\"\r\n [attr.id]=\"showTabs() ? 'fly-emoji-panel-emoji' : null\"\r\n [attr.aria-labelledby]=\"showTabs() ? 'fly-emoji-tab-emoji' : null\"\r\n [attr.aria-multiselectable]=\"selectionMode() === 'multi'\">\r\n @for (section of sections(); track section.id) {\r\n <div class=\"fly-emoji-picker__section\" role=\"group\" [attr.aria-label]=\"sectionLabel(section)\">\r\n <h4 class=\"fly-emoji-picker__heading\">{{ sectionLabel(section) }}</h4>\r\n <div class=\"fly-emoji-picker__grid\">\r\n @for (cell of section.cells; track cell.entry.id) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__cell\"\r\n role=\"option\"\r\n [class.is-active]=\"cell.selected\"\r\n [attr.aria-selected]=\"cell.selected\"\r\n [flyTooltip]=\"cell.entry.label\"\r\n [attr.aria-label]=\"cell.entry.label\"\r\n (click)=\"pick(cell.entry)\">\r\n @if (section.animated) {\r\n <!-- Hover lives on the animated element, not the button: a template reference\r\n declared inside a control-flow block isn't visible to the block's parent. -->\r\n <fly-animated-emoji\r\n #anim\r\n class=\"fly-emoji-picker__glyph\"\r\n [id]=\"cell.entry.id\"\r\n [size]=\"20\"\r\n (pointerenter)=\"onCellHover(anim)\"\r\n (pointerleave)=\"cancelHover()\" />\r\n } @else {\r\n <span class=\"fly-emoji-picker__glyph\">{{ cell.glyph }}</span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n } @empty {\r\n <p class=\"fly-emoji-picker__empty\">{{ 'agent.lookup.no_results' | translate }}</p>\r\n }\r\n\r\n <!-- Lives INSIDE the scroller, as the last row, rather than pinned below it \u2014 a static\r\n footer row permanently ate into the vertical space actual emoji rows could use;\r\n scrolled into view instead, it only costs space once you reach the end. -->\r\n @if (hasMore()) {\r\n <button type=\"button\" class=\"fly-emoji-picker__more\" (click)=\"toggleExpanded()\">\r\n {{ (isExpanded() ? 'common.label.emoji_show_less' : 'common.label.emoji_show_more') | translate }}\r\n </button>\r\n }\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block;block-size:100%;-webkit-backdrop-filter:var(--glass2-blur);backdrop-filter:var(--glass2-blur)}@media(prefers-reduced-transparency:reduce){:host{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}:host:before,:host:after{display:none}}@media(prefers-contrast:more){:host{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){:host:after{animation:none}}:host{--fly-emoji-cell: 36px;--fly-emoji-cell-gap: 6px;--fly-emoji-glyph: 17px;--fly-emoji-section-gap: 14px;--fly-emoji-bar-gap: 8px;--fly-emoji-search-font: 12px;--fly-emoji-search-padding: 8px 10px;--fly-emoji-heading-display: block}:host(.fly-emoji-picker--medium){--fly-emoji-cell: 30px;--fly-emoji-cell-gap: 5px;--fly-emoji-glyph: 16px;--fly-emoji-section-gap: 10px;--fly-emoji-bar-gap: 6px;--fly-emoji-search-font: 11.5px;--fly-emoji-search-padding: 6px 9px}:host(.fly-emoji-picker--small){--fly-emoji-cell: 24px;--fly-emoji-cell-gap: 3px;--fly-emoji-glyph: 15px;--fly-emoji-section-gap: 6px;--fly-emoji-bar-gap: 5px;--fly-emoji-search-font: 11px;--fly-emoji-search-padding: 4px 7px;--fly-emoji-heading-display: none;--fly-emoji-picker-list-max: 132px}.fly-emoji-picker{display:flex;flex-direction:column;gap:var(--fly-emoji-bar-gap);box-sizing:border-box;block-size:100%;color:var(--text-color, inherit)}.fly-emoji-picker__bar{display:flex;align-items:center;gap:10px}.fly-emoji-picker__search{flex:1 1 auto;min-inline-size:0;box-sizing:border-box;padding:var(--fly-emoji-search-padding);font:inherit;font-size:var(--fly-emoji-search-font);border:1px solid var(--surface-border, rgba(255, 255, 255, .12));border-radius:5px;background:transparent;color:var(--text-color, inherit);outline:none}.fly-emoji-picker__search:focus{border-color:var(--primary-color)}.fly-emoji-picker__list{display:flex;flex:1 1 auto;flex-direction:column;gap:var(--fly-emoji-section-gap);min-block-size:0;max-block-size:var(--fly-emoji-picker-list-max, 320px);overflow-y:auto;padding-inline-end:4px}.fly-emoji-picker__section{display:flex;flex-direction:column;gap:6px}.fly-emoji-picker__heading{display:var(--fly-emoji-heading-display);position:sticky;inset-block-start:0;z-index:1;margin:0;padding-block:3px;font-size:10px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:var(--text-color-secondary, rgba(235, 235, 245, .55));background:var(--surface-overlay, var(--surface-card, #1c1c1e))}.fly-emoji-picker__tones{position:relative;flex:0 0 auto}.fly-emoji-picker__tone-trigger{display:flex;align-items:center;gap:2px;inline-size:auto;block-size:22px;padding:0 4px;font-size:14px;line-height:1;border:1px solid var(--surface-border, rgba(255, 255, 255, .12));border-radius:5px;background:transparent;color:var(--text-color-secondary, rgba(235, 235, 245, .55));cursor:pointer;transition:background .1s ease,border-color .1s ease}.fly-emoji-picker__tone-trigger:hover{background:var(--surface-hover, rgba(255, 255, 255, .08))}.fly-emoji-picker__tone-trigger:focus-visible{outline:2px solid var(--primary-color);outline-offset:-1px}.fly-emoji-picker__tone-glyph{line-height:1}.fly-emoji-picker__tone-caret{font-size:8px}.fly-emoji-picker__tone-menu{position:absolute;inset-block-start:calc(100% + 4px);inset-inline-end:0;z-index:21;display:grid;grid-template-columns:repeat(3,1fr);gap:2px;padding:4px;border-radius:8px;border:1px solid var(--glass2-border);background-image:radial-gradient(70% 60% at 50% 100%,var(--w07),transparent 70%),linear-gradient(180deg,var(--w08),transparent 20%),linear-gradient(180deg,var(--mat-menu-solid-a),var(--mat-menu-solid-b));box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset)}@media(prefers-reduced-transparency:reduce){.fly-emoji-picker__tone-menu{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.fly-emoji-picker__tone-menu:before,.fly-emoji-picker__tone-menu:after{display:none}}@media(prefers-contrast:more){.fly-emoji-picker__tone-menu{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.fly-emoji-picker__tone-menu:after{animation:none}}.fly-emoji-picker__tone-option{display:grid;place-items:center;inline-size:26px;block-size:26px;padding:0;font-size:15px;line-height:1;border:1px solid transparent;border-radius:5px;background:transparent;cursor:pointer;transition:background .1s ease,border-color .1s ease}.fly-emoji-picker__tone-option:hover{background:var(--surface-hover, rgba(255, 255, 255, .08))}.fly-emoji-picker__tone-option:focus-visible{outline:2px solid var(--primary-color);outline-offset:-1px}.fly-emoji-picker__tone-option.is-active{border-color:var(--primary-color);background:var(--surface-hover, rgba(255, 255, 255, .1))}.fly-emoji-picker__grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(var(--fly-emoji-cell),1fr));gap:var(--fly-emoji-cell-gap)}.fly-emoji-picker__cell{display:flex;align-items:center;justify-content:center;aspect-ratio:1;min-block-size:var(--fly-emoji-cell);padding:4px;border:1px solid transparent;border-radius:6px;background:transparent;color:var(--text-color, inherit);cursor:pointer;transition:background .1s ease,border-color .1s ease}.fly-emoji-picker__cell:hover{background:var(--surface-hover, rgba(255, 255, 255, .08));border-color:var(--surface-border, rgba(255, 255, 255, .12))}.fly-emoji-picker__cell:focus-visible{outline:2px solid var(--primary-color);outline-offset:-1px}.fly-emoji-picker__cell.is-active{border-color:var(--primary-color);background:var(--selection-bg, rgba(0, 113, 227, .24))}.fly-emoji-picker__glyph{direction:ltr;font-size:var(--fly-emoji-glyph);line-height:1}.fly-emoji-picker__more{align-self:flex-start;padding:3px 8px;font:inherit;font-size:11px;border:none;border-radius:999px;background:transparent;color:var(--primary-color);cursor:pointer}.fly-emoji-picker__more:hover{background:var(--surface-hover, rgba(255, 255, 255, .06))}.fly-emoji-picker__more:focus-visible{outline:2px solid var(--primary-color);outline-offset:1px}.fly-emoji-picker__empty{margin:0;padding:12px 4px;text-align:center;font-size:12px;color:var(--text-color-secondary, rgba(235, 235, 245, .5))}.fly-emoji-picker__tabs{display:flex;gap:4px;border-block-end:1px solid var(--surface-border, rgba(255, 255, 255, .12))}.fly-emoji-picker__tab{appearance:none;padding:6px 12px;font:inherit;font-size:12px;border:none;border-block-end:2px solid transparent;background:transparent;color:var(--text-color-secondary, rgba(235, 235, 245, .6));cursor:pointer}.fly-emoji-picker__tab.is-active{color:var(--text-color, inherit);border-block-end-color:var(--primary-color)}.fly-emoji-picker__tab:focus-visible{outline:2px solid var(--primary-color);outline-offset:-2px}.fly-emoji-picker__stickers{flex:1 1 auto;min-block-size:0;overflow-y:auto;display:grid;grid-template-columns:repeat(auto-fill,minmax(72px,1fr));gap:6px;padding:4px}.fly-emoji-picker__sticker{appearance:none;display:flex;align-items:center;justify-content:center;padding:4px;border:none;border-radius:10px;background:transparent;cursor:pointer}.fly-emoji-picker__sticker:hover,.fly-emoji-picker__sticker:focus-visible{background:var(--surface-hover, rgba(255, 255, 255, .08))}.fly-emoji-picker__sticker:focus-visible{outline:2px solid var(--primary-color);outline-offset:-2px}\n"], dependencies: [{ kind: "directive", type: FlyClickOutsideDirective, selector: "[flyClickOutside]", inputs: ["flyClickOutsideEnabled"], outputs: ["flyClickOutside"] }, { kind: "directive", type: FlyTooltipDirective, selector: "[flyTooltip], [data-tooltip]", inputs: ["flyTooltip", "flyTooltipPlacement", "flyTooltipDisabled", "flyTooltipDelay"] }, { kind: "component", type: FlyAnimatedEmojiComponent, selector: "fly-animated-emoji", inputs: ["id", "size", "autoplayOnFirstVisible", "playOnHover"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7882
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyEmojiPickerComponent, isStandalone: true, selector: "fly-emoji-picker", inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, selectionMode: { classPropertyName: "selectionMode", publicName: "selectionMode", isSignal: true, isRequired: false, transformFunction: null }, selectedIds: { classPropertyName: "selectedIds", publicName: "selectedIds", isSignal: true, isRequired: false, transformFunction: null }, categories: { classPropertyName: "categories", publicName: "categories", isSignal: true, isRequired: false, transformFunction: null }, defaultCategory: { classPropertyName: "defaultCategory", publicName: "defaultCategory", isSignal: true, isRequired: false, transformFunction: null }, skinTones: { classPropertyName: "skinTones", publicName: "skinTones", isSignal: true, isRequired: false, transformFunction: null }, skinTone: { classPropertyName: "skinTone", publicName: "skinTone", isSignal: true, isRequired: false, transformFunction: null }, showMore: { classPropertyName: "showMore", publicName: "showMore", isSignal: true, isRequired: false, transformFunction: null }, previewCount: { classPropertyName: "previewCount", publicName: "previewCount", isSignal: true, isRequired: false, transformFunction: null }, emojiAnimation: { classPropertyName: "emojiAnimation", publicName: "emojiAnimation", isSignal: true, isRequired: false, transformFunction: null }, dismissOnOutsidePress: { classPropertyName: "dismissOnOutsidePress", publicName: "dismissOnOutsidePress", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { stickerPicked: "stickerPicked", toggled: "toggled", selectionChange: "selectionChange", skinToneChange: "skinToneChange", dismissed: "dismissed" }, host: { properties: { "class.fly-emoji-picker--small": "size() === 'small'", "class.fly-emoji-picker--medium": "size() === 'medium'", "class.fly-emoji-picker--large": "size() === 'large'" } }, viewQueries: [{ propertyName: "searchRef", first: true, predicate: ["searchRef"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\r\n class=\"fly-emoji-picker\"\r\n [flyClickOutsideEnabled]=\"dismissOnOutsidePress()\"\r\n (flyClickOutside)=\"dismissed.emit()\"\r\n [attr.aria-label]=\"'common.label.emoji_picker' | translate\">\r\n <!-- Stickers mode only: the emoji grid is unchanged, stickers are an ADDITIONAL surface. -->\r\n @if (showTabs()) {\r\n <div\r\n class=\"fly-emoji-picker__tabs\"\r\n role=\"tablist\"\r\n [attr.aria-label]=\"'common.label.emoji_tabs' | translate\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__tab\"\r\n role=\"tab\"\r\n id=\"fly-emoji-tab-emoji\"\r\n aria-controls=\"fly-emoji-panel-emoji\"\r\n [class.is-active]=\"activeTab() === 'emoji'\"\r\n [attr.aria-selected]=\"activeTab() === 'emoji'\"\r\n [attr.tabindex]=\"activeTab() === 'emoji' ? 0 : -1\"\r\n (keydown)=\"onTabKeydown($event)\"\r\n (click)=\"selectTab('emoji')\">\r\n {{ 'common.label.emoji_tab_emoji' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__tab\"\r\n role=\"tab\"\r\n id=\"fly-emoji-tab-stickers\"\r\n aria-controls=\"fly-emoji-panel-stickers\"\r\n [class.is-active]=\"activeTab() === 'stickers'\"\r\n [attr.aria-selected]=\"activeTab() === 'stickers'\"\r\n [attr.tabindex]=\"activeTab() === 'stickers' ? 0 : -1\"\r\n (keydown)=\"onTabKeydown($event)\"\r\n (click)=\"selectTab('stickers')\">\r\n {{ 'common.label.emoji_tab_stickers' | translate }}\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (showTabs() && activeTab() === 'stickers') {\r\n <div\r\n class=\"fly-emoji-picker__stickers\"\r\n id=\"fly-emoji-panel-stickers\"\r\n role=\"tabpanel\"\r\n aria-labelledby=\"fly-emoji-tab-stickers\">\r\n @for (entry of stickers(); track entry.id) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__sticker\"\r\n [flyTooltip]=\"entry.label\"\r\n [attr.aria-label]=\"entry.label\"\r\n (pointerenter)=\"onCellHover(sticker)\"\r\n (pointerleave)=\"cancelHover()\"\r\n (click)=\"pickSticker(entry)\">\r\n <fly-animated-emoji #sticker [id]=\"entry.id\" [size]=\"64\" />\r\n </button>\r\n } @empty {\r\n <p class=\"fly-emoji-picker__empty\">{{ 'agent.lookup.no_results' | translate }}</p>\r\n }\r\n </div>\r\n }\r\n\r\n @if (!showTabs() || activeTab() === 'emoji') {\r\n <!-- Search + tone sit together as one chrome bar: the tone swatches are hands, and on their own row\r\n above the grid they read as pickable content rather than a setting. -->\r\n <div class=\"fly-emoji-picker__bar\">\r\n <input\r\n class=\"fly-emoji-picker__search\"\r\n type=\"text\"\r\n #searchRef\r\n [value]=\"query()\"\r\n (input)=\"onQueryInput(searchRef.value)\"\r\n [placeholder]=\"'common.label.search_emoji' | translate\"\r\n [attr.aria-label]=\"'common.label.search_emoji' | translate\"\r\n spellcheck=\"false\"\r\n autocomplete=\"off\" />\r\n\r\n @if (showToneStrip()) {\r\n <!-- Escape-to-close listener only \u2014 every actual control inside (trigger + options) is its\r\n own focusable button, so this wrapper itself doesn't need to be. -->\r\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\r\n <div\r\n class=\"fly-emoji-picker__tones\"\r\n [flyClickOutsideEnabled]=\"toneMenuOpen()\"\r\n (flyClickOutside)=\"toneMenuOpen.set(false)\"\r\n (keydown.escape)=\"toneMenuOpen.set(false)\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__tone-trigger\"\r\n aria-haspopup=\"listbox\"\r\n [attr.aria-expanded]=\"toneMenuOpen()\"\r\n [attr.aria-label]=\"'common.label.skin_tone' | translate\"\r\n [flyTooltip]=\"toneLabelKey(effectiveTone()) | translate\"\r\n (click)=\"toneMenuOpen.set(!toneMenuOpen())\">\r\n <span class=\"fly-emoji-picker__tone-glyph\">{{ toneSwatch(effectiveTone()) }}</span>\r\n <i class=\"pi pi-chevron-down fly-emoji-picker__tone-caret\" aria-hidden=\"true\"></i>\r\n </button>\r\n\r\n @if (toneMenuOpen()) {\r\n <div\r\n class=\"fly-emoji-picker__tone-menu\"\r\n role=\"listbox\"\r\n [attr.aria-label]=\"'common.label.skin_tone' | translate\">\r\n @for (tone of tones; track tone) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__tone-option\"\r\n role=\"option\"\r\n [class.is-active]=\"effectiveTone() === tone\"\r\n [attr.aria-selected]=\"effectiveTone() === tone\"\r\n [flyTooltip]=\"toneLabelKey(tone) | translate\"\r\n (click)=\"onToneOptionClick(tone)\">\r\n {{ toneSwatch(tone) }}\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- ONE scrolling list; each category is a separator label over its own grid. Tabs wrapped to three\r\n rows in a sidebar drawer and cost a click to reach any group. -->\r\n <div\r\n class=\"fly-emoji-picker__list\"\r\n role=\"listbox\"\r\n [attr.id]=\"showTabs() ? 'fly-emoji-panel-emoji' : null\"\r\n [attr.aria-labelledby]=\"showTabs() ? 'fly-emoji-tab-emoji' : null\"\r\n [attr.aria-multiselectable]=\"selectionMode() === 'multi'\">\r\n @for (section of sections(); track section.id) {\r\n <div class=\"fly-emoji-picker__section\" role=\"group\" [attr.aria-label]=\"sectionLabel(section)\">\r\n <h4 class=\"fly-emoji-picker__heading\">{{ sectionLabel(section) }}</h4>\r\n <div class=\"fly-emoji-picker__grid\">\r\n @for (cell of section.cells; track cell.entry.id) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__cell\"\r\n role=\"option\"\r\n [class.is-active]=\"cell.selected\"\r\n [attr.aria-selected]=\"cell.selected\"\r\n [flyTooltip]=\"cell.entry.label\"\r\n [attr.aria-label]=\"cell.entry.label\"\r\n (click)=\"pick(cell.entry)\">\r\n @if (section.animated) {\r\n <!-- Hover lives on the animated element, not the button: a template reference\r\n declared inside a control-flow block isn't visible to the block's parent. -->\r\n <fly-animated-emoji\r\n #anim\r\n class=\"fly-emoji-picker__glyph\"\r\n [id]=\"cell.entry.id\"\r\n [size]=\"20\"\r\n (pointerenter)=\"onCellHover(anim)\"\r\n (pointerleave)=\"cancelHover()\" />\r\n } @else {\r\n <span class=\"fly-emoji-picker__glyph\">{{ cell.glyph }}</span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n } @empty {\r\n <p class=\"fly-emoji-picker__empty\">{{ 'agent.lookup.no_results' | translate }}</p>\r\n }\r\n\r\n <!-- Lives INSIDE the scroller, as the last row, rather than pinned below it \u2014 a static\r\n footer row permanently ate into the vertical space actual emoji rows could use;\r\n scrolled into view instead, it only costs space once you reach the end. -->\r\n @if (hasMore()) {\r\n <button type=\"button\" class=\"fly-emoji-picker__more\" (click)=\"toggleExpanded()\">\r\n {{ (isExpanded() ? 'common.label.emoji_show_less' : 'common.label.emoji_show_more') | translate }}\r\n </button>\r\n }\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block;block-size:100%;-webkit-backdrop-filter:var(--glass2-blur);backdrop-filter:var(--glass2-blur)}@media(prefers-reduced-transparency:reduce){:host{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}:host:before,:host:after{display:none}}@media(prefers-contrast:more){:host{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){:host:after{animation:none}}:host{--fly-emoji-cell: 36px;--fly-emoji-cell-gap: 6px;--fly-emoji-glyph: 17px;--fly-emoji-section-gap: 14px;--fly-emoji-bar-gap: 8px;--fly-emoji-search-font: 12px;--fly-emoji-search-padding: 8px 10px;--fly-emoji-heading-display: block}:host(.fly-emoji-picker--medium){--fly-emoji-cell: 30px;--fly-emoji-cell-gap: 5px;--fly-emoji-glyph: 16px;--fly-emoji-section-gap: 10px;--fly-emoji-bar-gap: 6px;--fly-emoji-search-font: 11.5px;--fly-emoji-search-padding: 6px 9px}:host(.fly-emoji-picker--small){--fly-emoji-cell: 24px;--fly-emoji-cell-gap: 3px;--fly-emoji-glyph: 15px;--fly-emoji-section-gap: 6px;--fly-emoji-bar-gap: 5px;--fly-emoji-search-font: 11px;--fly-emoji-search-padding: 4px 7px;--fly-emoji-heading-display: none;--fly-emoji-picker-list-max: 132px}.fly-emoji-picker{display:flex;flex-direction:column;gap:var(--fly-emoji-bar-gap);box-sizing:border-box;block-size:100%;color:var(--text-color, inherit)}.fly-emoji-picker__bar{display:flex;align-items:center;gap:10px}.fly-emoji-picker__search{flex:1 1 auto;min-inline-size:0;box-sizing:border-box;padding:var(--fly-emoji-search-padding);font:inherit;font-size:var(--fly-emoji-search-font);border:1px solid var(--surface-border, rgba(255, 255, 255, .12));border-radius:5px;background:transparent;color:var(--text-color, inherit);outline:none}.fly-emoji-picker__search:focus{border-color:var(--primary-color)}.fly-emoji-picker__list{display:flex;flex:1 1 auto;flex-direction:column;gap:var(--fly-emoji-section-gap);min-block-size:0;max-block-size:var(--fly-emoji-picker-list-max, 320px);overflow-y:auto;padding-inline-end:4px}.fly-emoji-picker__section{display:flex;flex-direction:column;gap:6px}.fly-emoji-picker__heading{display:var(--fly-emoji-heading-display);position:sticky;inset-block-start:0;z-index:1;margin:0;padding-block:3px;font-size:10px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:var(--text-color-secondary, rgba(235, 235, 245, .55));background:var(--surface-overlay, var(--surface-card, #1c1c1e))}.fly-emoji-picker__tones{position:relative;flex:0 0 auto}.fly-emoji-picker__tone-trigger{display:flex;align-items:center;gap:2px;inline-size:auto;block-size:22px;padding:0 4px;font-size:14px;line-height:1;border:1px solid var(--surface-border, rgba(255, 255, 255, .12));border-radius:5px;background:transparent;color:var(--text-color-secondary, rgba(235, 235, 245, .55));cursor:pointer;transition:background .1s ease,border-color .1s ease}.fly-emoji-picker__tone-trigger:hover{background:var(--surface-hover, rgba(255, 255, 255, .08))}.fly-emoji-picker__tone-trigger:focus-visible{outline:2px solid var(--primary-color);outline-offset:-1px}.fly-emoji-picker__tone-glyph{line-height:1}.fly-emoji-picker__tone-caret{font-size:8px}.fly-emoji-picker__tone-menu{position:absolute;inset-block-start:calc(100% + 4px);inset-inline-end:0;z-index:21;display:grid;grid-template-columns:repeat(3,1fr);gap:2px;padding:4px;border-radius:8px;border:1px solid var(--glass2-border);background-image:radial-gradient(70% 60% at 50% 100%,var(--w07),transparent 70%),linear-gradient(180deg,var(--w08),transparent 20%),linear-gradient(180deg,var(--mat-menu-solid-a),var(--mat-menu-solid-b));box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset)}@media(prefers-reduced-transparency:reduce){.fly-emoji-picker__tone-menu{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.fly-emoji-picker__tone-menu:before,.fly-emoji-picker__tone-menu:after{display:none}}@media(prefers-contrast:more){.fly-emoji-picker__tone-menu{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.fly-emoji-picker__tone-menu:after{animation:none}}.fly-emoji-picker__tone-option{display:grid;place-items:center;inline-size:26px;block-size:26px;padding:0;font-size:15px;line-height:1;border:1px solid transparent;border-radius:5px;background:transparent;cursor:pointer;transition:background .1s ease,border-color .1s ease}.fly-emoji-picker__tone-option:hover{background:var(--surface-hover, rgba(255, 255, 255, .08))}.fly-emoji-picker__tone-option:focus-visible{outline:2px solid var(--primary-color);outline-offset:-1px}.fly-emoji-picker__tone-option.is-active{border-color:var(--primary-color);background:var(--surface-hover, rgba(255, 255, 255, .1))}.fly-emoji-picker__grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(var(--fly-emoji-cell),1fr));gap:var(--fly-emoji-cell-gap)}.fly-emoji-picker__cell{display:flex;align-items:center;justify-content:center;aspect-ratio:1;min-block-size:var(--fly-emoji-cell);padding:4px;border:1px solid transparent;border-radius:6px;background:transparent;color:var(--text-color, inherit);cursor:pointer;transition:background .1s ease,border-color .1s ease}.fly-emoji-picker__cell:hover{background:var(--surface-hover, rgba(255, 255, 255, .08));border-color:var(--surface-border, rgba(255, 255, 255, .12))}.fly-emoji-picker__cell:focus-visible{outline:2px solid var(--primary-color);outline-offset:-1px}.fly-emoji-picker__cell.is-active{border-color:var(--primary-color);background:var(--selection-bg, rgba(0, 113, 227, .24))}.fly-emoji-picker__glyph{direction:ltr;font-size:var(--fly-emoji-glyph);line-height:1}.fly-emoji-picker__more{align-self:flex-start;padding:3px 8px;font:inherit;font-size:11px;border:none;border-radius:999px;background:transparent;color:var(--primary-color);cursor:pointer}.fly-emoji-picker__more:hover{background:var(--surface-hover, rgba(255, 255, 255, .06))}.fly-emoji-picker__more:focus-visible{outline:2px solid var(--primary-color);outline-offset:1px}.fly-emoji-picker__empty{margin:0;padding:12px 4px;text-align:center;font-size:12px;color:var(--text-color-secondary, rgba(235, 235, 245, .5))}.fly-emoji-picker__tabs{display:flex;gap:4px;border-block-end:1px solid var(--surface-border, rgba(255, 255, 255, .12))}.fly-emoji-picker__tab{appearance:none;padding:6px 12px;font:inherit;font-size:12px;border:none;border-block-end:2px solid transparent;background:transparent;color:var(--text-color-secondary, rgba(235, 235, 245, .6));cursor:pointer}.fly-emoji-picker__tab.is-active{color:var(--text-color, inherit);border-block-end-color:var(--primary-color)}.fly-emoji-picker__tab:focus-visible{outline:2px solid var(--primary-color);outline-offset:-2px}.fly-emoji-picker__stickers{flex:1 1 auto;min-block-size:0;overflow-y:auto;display:grid;grid-template-columns:repeat(auto-fill,minmax(72px,1fr));gap:6px;padding:4px}.fly-emoji-picker__sticker{appearance:none;display:flex;align-items:center;justify-content:center;padding:4px;border:none;border-radius:10px;background:transparent;cursor:pointer}.fly-emoji-picker__sticker:hover,.fly-emoji-picker__sticker:focus-visible{background:var(--surface-hover, rgba(255, 255, 255, .08))}.fly-emoji-picker__sticker:focus-visible{outline:2px solid var(--primary-color);outline-offset:-2px}\n"], dependencies: [{ kind: "directive", type: FlyClickOutsideDirective, selector: "[flyClickOutside]", inputs: ["flyClickOutsideEnabled", "flyClickOutsideIgnore"], outputs: ["flyClickOutside"] }, { kind: "directive", type: FlyTooltipDirective, selector: "[flyTooltip], [data-tooltip]", inputs: ["flyTooltip", "flyTooltipPlacement", "flyTooltipDisabled", "flyTooltipDelay"] }, { kind: "component", type: FlyAnimatedEmojiComponent, selector: "fly-animated-emoji", inputs: ["id", "size", "autoplayOnFirstVisible", "playOnHover"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7861
7883
  }
7862
7884
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyEmojiPickerComponent, decorators: [{
7863
7885
  type: Component,
@@ -13542,7 +13564,7 @@ class FlyGanttComponent {
13542
13564
  : t('gantt.aria.bar', { label: row.label, start: toIsoDate(start), end: toIsoDate(end), progress });
13543
13565
  }
13544
13566
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyGanttComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
13545
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyGanttComponent, isStandalone: true, selector: "fly-gantt", inputs: { rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, dependencies: { classPropertyName: "dependencies", publicName: "dependencies", isSignal: true, isRequired: false, transformFunction: null }, zoom: { classPropertyName: "zoom", publicName: "zoom", isSignal: true, isRequired: false, transformFunction: null }, showToday: { classPropertyName: "showToday", publicName: "showToday", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, labelWidth: { classPropertyName: "labelWidth", publicName: "labelWidth", isSignal: true, isRequired: false, transformFunction: null }, resizableLabels: { classPropertyName: "resizableLabels", publicName: "resizableLabels", isSignal: true, isRequired: false, transformFunction: null }, rowHeight: { classPropertyName: "rowHeight", publicName: "rowHeight", isSignal: true, isRequired: false, transformFunction: null }, maxRows: { classPropertyName: "maxRows", publicName: "maxRows", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rowDatesChange: "rowDatesChange", dependencyCreate: "dependencyCreate", dependencyDelete: "dependencyDelete", rowClick: "rowClick", rowDblClick: "rowDblClick", labelWidthChange: "labelWidthChange" }, host: { properties: { "class.fly-gantt--rtl": "rtl()", "class.fly-gantt--readonly": "readonly()", "class.fly-gantt--resizing": "resizingLabels()", "attr.dir": "i18n.direction()" }, classAttribute: "fly-gantt" }, viewQueries: [{ propertyName: "bodyRef", first: true, predicate: ["bodySvg"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- Cross-scrolling canvas: a CSS grid whose header row and label column are sticky, so the\n time grid scrolls under a pinned header + label tree. All SVG geometry is pre-mirrored for\n RTL in the component (see gantt-scale mapX), so this template never branches on direction. -->\n<div\n class=\"fly-gantt__scroll\"\n role=\"grid\"\n tabindex=\"0\"\n [attr.aria-label]=\"'gantt.aria.grid' | translate\"\n [attr.aria-rowcount]=\"visibleRows().length\"\n (keydown)=\"onGridKeydown($event)\"\n>\n <div\n class=\"fly-gantt__canvas\"\n [style.grid-template-columns]=\"effectiveLabelWidth() + 'px ' + innerWidth() + 'px'\"\n [style.grid-template-rows]=\"HEADER_H + 'px ' + bodyHeight() + 'px'\"\n >\n <!-- \u2500\u2500 Corner (pinned both axes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"fly-gantt__corner\" [style.height.px]=\"HEADER_H\">\n @if (resizableLabels()) {\n <!-- The divider lives in the corner because the corner is the one cell pinned on BOTH\n axes: the grip stays reachable however far the user has scrolled. The full-height\n strip down the label pane below is the same gesture, presentational only. -->\n <div\n class=\"fly-gantt__splitter\"\n role=\"separator\"\n tabindex=\"0\"\n aria-orientation=\"vertical\"\n [attr.aria-label]=\"'gantt.aria.label_resize' | translate\"\n [attr.aria-valuenow]=\"effectiveLabelWidth()\"\n [attr.aria-valuemin]=\"MIN_LABEL_W\"\n [attr.aria-valuemax]=\"MAX_LABEL_W\"\n (pointerdown)=\"onLabelResizePointerDown($event)\"\n (keydown)=\"onLabelResizeKeydown($event)\"\n (dblclick)=\"resetLabelWidth()\"\n ></div>\n }\n </div>\n\n <!-- \u2500\u2500 Time header (pinned top) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <svg\n class=\"fly-gantt__time-header\"\n [attr.width]=\"innerWidth()\"\n [attr.height]=\"HEADER_H\"\n [attr.viewBox]=\"'0 0 ' + innerWidth() + ' ' + HEADER_H\"\n aria-hidden=\"true\"\n >\n @for (b of weekendBands(); track b.key) {\n <rect class=\"fly-gantt__weekend\" [attr.x]=\"b.x\" [attr.y]=\"HEADER_UPPER_H\" [attr.width]=\"b.width\" [attr.height]=\"HEADER_LOWER_H\" />\n }\n @for (t of upperTicks(); track t.key) {\n <line class=\"fly-gantt__tick-line\" [attr.x1]=\"t.x\" [attr.y1]=\"0\" [attr.x2]=\"t.x\" [attr.y2]=\"HEADER_H\" />\n <text class=\"fly-gantt__tick-label fly-gantt__tick-label--upper\" [attr.x]=\"t.x + t.width / 2\" [attr.y]=\"HEADER_UPPER_H / 2 + 4\">{{ t.label }}</text>\n }\n @for (t of lowerTicks(); track t.key) {\n <line class=\"fly-gantt__tick-line\" [attr.x1]=\"t.x\" [attr.y1]=\"HEADER_UPPER_H\" [attr.x2]=\"t.x\" [attr.y2]=\"HEADER_H\" />\n <text class=\"fly-gantt__tick-label\" [attr.x]=\"t.x + t.width / 2\" [attr.y]=\"HEADER_UPPER_H + HEADER_LOWER_H / 2 + 4\">{{ t.label }}</text>\n }\n @if (todayX() !== null) {\n <line class=\"fly-gantt__today\" [attr.x1]=\"todayX()\" [attr.y1]=\"HEADER_UPPER_H\" [attr.x2]=\"todayX()\" [attr.y2]=\"HEADER_H\" />\n }\n </svg>\n\n <!-- \u2500\u2500 Label tree (pinned inline-start) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"fly-gantt__labels\" role=\"rowgroup\">\n @for (vm of rowVms(); track vm.flat.row.id) {\n <div\n class=\"fly-gantt__label-row\"\n role=\"row\"\n tabindex=\"-1\"\n [class.fly-gantt__label-row--selected]=\"selectedId() === vm.flat.row.id\"\n [class.fly-gantt__label-row--tinted]=\"vm.tint !== null\"\n [style.height.px]=\"rowHeight()\"\n [style.--fly-gantt-row-tint]=\"vm.tint\"\n [attr.aria-selected]=\"selectedId() === vm.flat.row.id\"\n [attr.aria-label]=\"vm.ariaLabel\"\n (click)=\"onRowClick(vm.flat.row.id)\"\n (dblclick)=\"onRowDblClick(vm.flat.row.id)\"\n (keydown.enter)=\"onLabelRowKeydown($event, vm.flat.row.id)\"\n >\n <span class=\"fly-gantt__label-inner\" [style.padding-inline-start.px]=\"indentFor(vm.flat.depth)\">\n @if (vm.flat.hasChildren) {\n <button\n type=\"button\"\n class=\"fly-gantt__chevron\"\n [class.fly-gantt__chevron--collapsed]=\"isCollapsed(vm.flat.row.id)\"\n [attr.aria-label]=\"(isCollapsed(vm.flat.row.id) ? 'gantt.expand' : 'gantt.collapse') | translate\"\n [attr.aria-expanded]=\"!isCollapsed(vm.flat.row.id)\"\n (click)=\"toggleCollapse(vm.flat.row.id, $event)\"\n >\u25B8</button>\n } @else {\n <span class=\"fly-gantt__chevron-spacer\"></span>\n }\n @if (vm.color) {\n <!-- Colour chip: the row's own colour at full strength, so a milestone stays\n identifiable in the label pane where the band tint is deliberately faint. -->\n <span class=\"fly-gantt__label-swatch\" [style.background]=\"vm.color\" aria-hidden=\"true\"></span>\n }\n <span\n class=\"fly-gantt__label-text\"\n [class.fly-gantt__label-text--group]=\"vm.shape === 'group'\"\n [title]=\"vm.flat.row.label\"\n >{{ vm.flat.row.label }}</span>\n </span>\n </div>\n }\n @if (resizableLabels()) {\n <!-- aria-hidden so this presentational twin of the corner separator is not a second\n (and structurally invalid) child of the rowgroup in the accessibility tree. -->\n <div\n class=\"fly-gantt__splitter fly-gantt__splitter--rail\"\n aria-hidden=\"true\"\n (pointerdown)=\"onLabelResizePointerDown($event)\"\n (dblclick)=\"resetLabelWidth()\"\n ></div>\n }\n </div>\n\n <!-- \u2500\u2500 Time grid body \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <svg\n #bodySvg\n class=\"fly-gantt__body\"\n [attr.width]=\"innerWidth()\"\n [attr.height]=\"bodyHeight()\"\n [attr.viewBox]=\"'0 0 ' + innerWidth() + ' ' + bodyHeight()\"\n >\n <defs>\n <marker\n [attr.id]=\"markerId\"\n markerWidth=\"8\"\n markerHeight=\"8\"\n refX=\"6\"\n refY=\"4\"\n orient=\"auto\"\n markerUnits=\"userSpaceOnUse\"\n >\n <path class=\"fly-gantt__arrowhead\" d=\"M0,0 L7,4 L0,8 Z\" />\n </marker>\n </defs>\n\n <!-- Row colour bands sit at the very bottom of the stack, so the weekend shading and the\n gridlines below still read through them. -->\n @for (vm of tintedRows(); track vm.flat.row.id) {\n <rect\n class=\"fly-gantt__row-band\"\n [attr.x]=\"0\"\n [attr.y]=\"vm.y\"\n [attr.width]=\"innerWidth()\"\n [attr.height]=\"rowHeight()\"\n [style.fill]=\"vm.tint\"\n />\n }\n\n <!-- Weekend shading + vertical gridlines (behind the bars). -->\n @for (b of weekendBands(); track b.key) {\n <rect class=\"fly-gantt__weekend\" [attr.x]=\"b.x\" [attr.y]=\"0\" [attr.width]=\"b.width\" [attr.height]=\"bodyHeight()\" />\n }\n @for (t of lowerTicks(); track t.key) {\n <line class=\"fly-gantt__grid-line\" [attr.x1]=\"t.x\" [attr.y1]=\"0\" [attr.x2]=\"t.x\" [attr.y2]=\"bodyHeight()\" />\n }\n\n <!-- Rows: a full-width hit rect (click + link drop target) then the shape. -->\n @for (vm of rowVms(); track vm.flat.row.id) {\n <g class=\"fly-gantt__row\" [attr.data-row-id]=\"vm.flat.row.id\">\n <rect\n class=\"fly-gantt__row-hit\"\n [class.fly-gantt__row-hit--selected]=\"selectedId() === vm.flat.row.id\"\n [attr.x]=\"0\"\n [attr.y]=\"vm.y\"\n [attr.width]=\"innerWidth()\"\n [attr.height]=\"rowHeight()\"\n (pointerdown)=\"onRowClick(vm.flat.row.id)\"\n (dblclick)=\"onRowDblClick(vm.flat.row.id)\"\n />\n\n @switch (vm.shape) {\n @case ('group') {\n <path class=\"fly-gantt__group\" [attr.d]=\"groupPath(vm)\" [style.fill]=\"vm.color\" [style.stroke]=\"vm.color\" />\n }\n @case ('milestone') {\n <polygon\n class=\"fly-gantt__milestone\"\n [class.fly-gantt__milestone--editable]=\"vm.editable\"\n [attr.points]=\"diamondPoints(vm)\"\n [style.fill]=\"vm.color\"\n (pointerdown)=\"onBarPointerDown($event, vm, 'move')\"\n />\n @if (!readonly() && !vm.flat.row.readonly) {\n <!-- A diamond has no width, so one connector serves it; it reads as the finish\n end, which is what makes a milestone\u2192task drag the FS everyone expects. -->\n <circle\n class=\"fly-gantt__connector\"\n [attr.cx]=\"milestoneConnectorX(vm)\"\n [attr.cy]=\"vm.midY\"\n r=\"4\"\n [attr.aria-label]=\"'gantt.aria.link_handle' | translate: { label: vm.flat.row.label }\"\n (pointerdown)=\"onLinkPointerDown($event, vm, 'finish')\"\n />\n }\n }\n @case ('bar') {\n <g class=\"fly-gantt__bar-group\">\n <rect\n class=\"fly-gantt__bar\"\n [class.fly-gantt__bar--editable]=\"vm.editable\"\n [attr.x]=\"vm.x\"\n [attr.y]=\"barTop(vm)\"\n [attr.width]=\"vm.width\"\n [attr.height]=\"barHeight()\"\n rx=\"4\"\n [style.fill]=\"vm.color\"\n (pointerdown)=\"onBarPointerDown($event, vm, 'move')\"\n />\n @if (vm.progressWidth > 0) {\n <rect\n class=\"fly-gantt__progress\"\n [attr.x]=\"vm.x\"\n [attr.y]=\"barTop(vm)\"\n [attr.width]=\"vm.progressWidth\"\n [attr.height]=\"barHeight()\"\n rx=\"4\"\n />\n }\n @if (vm.editable) {\n <!-- Resize handles at both edges. -->\n <rect\n class=\"fly-gantt__handle\"\n [attr.x]=\"vm.x\"\n [attr.y]=\"barTop(vm)\"\n [attr.width]=\"HANDLE_W\"\n [attr.height]=\"barHeight()\"\n [attr.aria-label]=\"'gantt.aria.resize_start' | translate\"\n (pointerdown)=\"onBarPointerDown($event, vm, 'resize-start')\"\n />\n <rect\n class=\"fly-gantt__handle\"\n [attr.x]=\"vm.x + vm.width - HANDLE_W\"\n [attr.y]=\"barTop(vm)\"\n [attr.width]=\"HANDLE_W\"\n [attr.height]=\"barHeight()\"\n [attr.aria-label]=\"'gantt.aria.resize_end' | translate\"\n (pointerdown)=\"onBarPointerDown($event, vm, 'resize-end')\"\n />\n }\n @if (!readonly() && !vm.flat.row.readonly) {\n <!-- One connector per END. Dragging from the start edge yields an SS/SF link\n and from the finish edge an FS/FF one \u2014 the drop edge picks which. -->\n <circle\n class=\"fly-gantt__connector\"\n [attr.cx]=\"vm.startX\"\n [attr.cy]=\"vm.midY\"\n r=\"4\"\n [attr.aria-label]=\"'gantt.aria.link_handle_start' | translate: { label: vm.flat.row.label }\"\n (pointerdown)=\"onLinkPointerDown($event, vm, 'start')\"\n />\n <circle\n class=\"fly-gantt__connector\"\n [attr.cx]=\"vm.endX\"\n [attr.cy]=\"vm.midY\"\n r=\"4\"\n [attr.aria-label]=\"'gantt.aria.link_handle' | translate: { label: vm.flat.row.label }\"\n (pointerdown)=\"onLinkPointerDown($event, vm, 'finish')\"\n />\n }\n </g>\n }\n }\n </g>\n }\n\n <!-- Dependency arrows (skipped for missing / collapsed endpoints). Each carries a fat\n transparent twin so a 1.5px line is still clickable at pointer precision. -->\n @for (link of linkVms(); track link.key) {\n <g class=\"fly-gantt__link-group\" [class.fly-gantt__link-group--selected]=\"selectedLinkKey() === link.key\">\n <path\n class=\"fly-gantt__link-hit\"\n [attr.d]=\"link.path\"\n [attr.aria-label]=\"link.ariaLabel\"\n (click)=\"onLinkClick($event, link)\"\n />\n <path class=\"fly-gantt__link\" [attr.d]=\"link.path\" [attr.marker-end]=\"'url(#' + markerId + ')'\" />\n @if (selectedLinkKey() === link.key && !readonly()) {\n <g\n class=\"fly-gantt__link-delete\"\n [attr.transform]=\"'translate(' + link.badgeX + ',' + link.badgeY + ')'\"\n [attr.aria-label]=\"'gantt.aria.delete_link' | translate\"\n (click)=\"deleteLink($event, link)\"\n >\n <circle class=\"fly-gantt__link-delete-bg\" r=\"8\" />\n <path class=\"fly-gantt__link-delete-x\" d=\"M-3.5,-3.5 L3.5,3.5 M3.5,-3.5 L-3.5,3.5\" />\n </g>\n }\n </g>\n }\n\n <!-- In-flight link rubber band. -->\n @if (linkGesturePath(); as gp) {\n <path class=\"fly-gantt__link fly-gantt__link--ghost\" [attr.d]=\"gp\" [attr.marker-end]=\"'url(#' + markerId + ')'\" />\n }\n\n <!-- Today line (over the bars). -->\n @if (todayX() !== null) {\n <line class=\"fly-gantt__today\" [attr.x1]=\"todayX()\" [attr.y1]=\"0\" [attr.x2]=\"todayX()\" [attr.y2]=\"bodyHeight()\" />\n }\n </svg>\n </div>\n\n @if (visibleRows().length === 0) {\n <p class=\"fly-gantt__empty\">{{ 'gantt.no_data' | translate }}</p>\n }\n @if (overflowCount() > 0) {\n <p class=\"fly-gantt__overflow\">\n {{ 'gantt.overflow' | translate: { shown: maxRows(), total: maxRows() + overflowCount() } }}\n </p>\n }\n</div>\n\n<!-- Drag candidate tooltip (HTML overlay, follows the cursor). -->\n@if (dragTooltip(); as tip) {\n <div class=\"fly-gantt__tooltip\" [style.left.px]=\"tip.x\" [style.top.px]=\"tip.y\">{{ tip.text }}</div>\n}\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--bg-2, var(--surface-card, #fff));--_surface-alt: var(--bg-3, var(--glass-bg-elevated, #f8fafc));--_surface-hover: var(--bg-hover, var(--surface-hover, #f1f5f9));--_border: var(--w08, var(--surface-border, #e2e8f0));--_border-strong: var(--w1, var(--surface-border, #cbd5e1));--_grid-line: var(--line-3, var(--surface-border, #eef2f7));--_text: var(--ink, var(--text-color, #0f172a));--_text-subtle: var(--ink-3, var(--text-color-secondary, #64748b));--_text-faint: var(--ink-4, var(--text-color-secondary, #94a3b8));--_accent: var(--accent);--_bar: var(--_accent);--_bar-progress: color-mix(in srgb, var(--_accent) 72%, #000);--_weekend: color-mix(in srgb, var(--_text-subtle) 9%, transparent);--_today: var(--danger, #ef4444);--_link: var(--_text-subtle);--_radius: var(--r-lg, 8px);--_transition: var(--t-state, .12s ease);display:block;inline-size:100%;block-size:100%;min-block-size:200px;color:var(--_text);font-size:13px}:host(.fly-gantt--resizing){cursor:col-resize;-webkit-user-select:none;user-select:none}.fly-gantt__scroll{position:relative;inline-size:100%;block-size:100%;overflow:auto;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);box-shadow:var(--shadow-card, none);outline:none}.fly-gantt__scroll:focus-visible{outline:2px solid var(--_accent);outline-offset:-2px}.fly-gantt__canvas{display:grid;position:relative}.fly-gantt__corner{position:sticky;inset-block-start:0;inset-inline-start:0;z-index:4;background:var(--_surface-alt);-webkit-backdrop-filter:blur(12px) saturate(140%);backdrop-filter:blur(12px) saturate(140%);border-inline-end:1px solid var(--_border);border-block-end:1px solid var(--_border-strong)}.fly-gantt__time-header{position:sticky;inset-block-start:0;z-index:3;display:block;background:var(--_surface-alt);-webkit-backdrop-filter:blur(12px) saturate(140%);backdrop-filter:blur(12px) saturate(140%);border-block-end:1px solid var(--_border-strong)}.fly-gantt__tick-line{stroke:var(--_grid-line);stroke-width:1}.fly-gantt__tick-label{fill:var(--_text-subtle);font-size:11px;text-anchor:middle;dominant-baseline:middle}.fly-gantt__tick-label--upper{fill:var(--_text);font-weight:600}.fly-gantt__splitter{position:absolute;inset-block:0;inset-inline-end:-3px;inline-size:7px;z-index:5;cursor:col-resize;touch-action:none}.fly-gantt__splitter:after{content:\"\";position:absolute;inset-block:0;inset-inline-start:3px;inline-size:1px;background:transparent;transition:background var(--_transition)}.fly-gantt__splitter:hover:after,.fly-gantt__splitter:focus-visible:after{background:var(--_accent)}.fly-gantt__splitter:focus-visible{outline:2px solid var(--_accent);outline-offset:-1px}:host(.fly-gantt--resizing) .fly-gantt__splitter:after{background:var(--_accent)}.fly-gantt__labels{position:sticky;inset-inline-start:0;z-index:2;background:var(--_surface);border-inline-end:1px solid var(--_border)}.fly-gantt__label-row{position:relative;display:flex;align-items:center;border-block-end:1px solid var(--_grid-line);cursor:pointer;transition:background var(--_transition)}.fly-gantt__label-row:hover{background:var(--_surface-hover)}.fly-gantt__label-row--selected{background:var(--_surface-hover);box-shadow:inset 3px 0 0 0 var(--_accent)}.fly-gantt__label-row--tinted:before{content:\"\";position:absolute;inset:0;background:var(--fly-gantt-row-tint);pointer-events:none}.fly-gantt__label-inner{position:relative;display:flex;align-items:center;gap:4px;inline-size:100%;min-inline-size:0;padding-inline-end:8px}.fly-gantt__label-swatch{flex:0 0 auto;inline-size:8px;block-size:8px;border-radius:2px}.fly-gantt__chevron{flex:0 0 auto;inline-size:18px;block-size:18px;padding:0;border:none;background:transparent;color:var(--_text-subtle);cursor:pointer;line-height:1;transform:rotate(90deg);transition:transform var(--_transition)}.fly-gantt__chevron--collapsed{transform:rotate(0)}:host(.fly-gantt--rtl) .fly-gantt__chevron--collapsed{transform:rotate(180deg)}.fly-gantt__chevron:focus-visible{outline:2px solid var(--_accent);outline-offset:1px;border-radius:4px}.fly-gantt__chevron-spacer{flex:0 0 auto;inline-size:18px}.fly-gantt__label-text{min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-gantt__label-text--group{font-weight:600}.fly-gantt__body{display:block;touch-action:pan-x pan-y}.fly-gantt__row-band{pointer-events:none}.fly-gantt__weekend{fill:var(--_weekend)}.fly-gantt__grid-line{stroke:var(--_grid-line);stroke-width:1}.fly-gantt__row-hit{fill:transparent}.fly-gantt__row-hit--selected{fill:var(--_surface-hover)}.fly-gantt__bar{fill:var(--_bar);stroke:none}.fly-gantt__bar--editable{cursor:grab}.fly-gantt__progress{fill:var(--_bar-progress);pointer-events:none}.fly-gantt__handle{fill:transparent;cursor:ew-resize}.fly-gantt__connector{fill:var(--_surface);stroke:var(--_accent);stroke-width:1.5;cursor:crosshair;opacity:0;transition:opacity var(--_transition)}.fly-gantt__row:hover .fly-gantt__connector{opacity:1}.fly-gantt__milestone{fill:var(--_accent);stroke:var(--_surface);stroke-width:1}.fly-gantt__milestone--editable{cursor:grab}.fly-gantt__group{fill:var(--_text-subtle);stroke:var(--_text-subtle)}.fly-gantt__link{fill:none;stroke:var(--_link);stroke-width:1.5;pointer-events:none}.fly-gantt__link-hit{fill:none;stroke:transparent;stroke-width:11;pointer-events:stroke;cursor:pointer}.fly-gantt__link-group:hover .fly-gantt__link{stroke:var(--_text)}.fly-gantt__link-group--selected .fly-gantt__link{stroke:var(--_accent);stroke-width:2.5}.fly-gantt__link-delete-bg{fill:var(--_accent)}.fly-gantt__link-delete{cursor:pointer}.fly-gantt__link-delete-x{stroke:var(--_surface);stroke-width:1.75;stroke-linecap:round;fill:none;pointer-events:none}.fly-gantt__link--ghost{stroke-dasharray:4 3;opacity:.8}.fly-gantt__arrowhead{fill:var(--_link)}.fly-gantt__today{stroke:var(--_today);stroke-width:1.5;stroke-dasharray:3 3;pointer-events:none}.fly-gantt__empty,.fly-gantt__overflow{margin:0;padding:12px;color:var(--_text-faint);font-size:12px;text-align:center}.fly-gantt__tooltip{position:fixed;z-index:20;transform:translate(12px,16px);padding:4px 8px;border-radius:6px;background:var(--_text);color:var(--_surface);font-size:12px;white-space:nowrap;pointer-events:none;direction:ltr;unicode-bidi:isolate}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
13567
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyGanttComponent, isStandalone: true, selector: "fly-gantt", inputs: { rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, dependencies: { classPropertyName: "dependencies", publicName: "dependencies", isSignal: true, isRequired: false, transformFunction: null }, zoom: { classPropertyName: "zoom", publicName: "zoom", isSignal: true, isRequired: false, transformFunction: null }, showToday: { classPropertyName: "showToday", publicName: "showToday", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, labelWidth: { classPropertyName: "labelWidth", publicName: "labelWidth", isSignal: true, isRequired: false, transformFunction: null }, resizableLabels: { classPropertyName: "resizableLabels", publicName: "resizableLabels", isSignal: true, isRequired: false, transformFunction: null }, rowHeight: { classPropertyName: "rowHeight", publicName: "rowHeight", isSignal: true, isRequired: false, transformFunction: null }, maxRows: { classPropertyName: "maxRows", publicName: "maxRows", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rowDatesChange: "rowDatesChange", dependencyCreate: "dependencyCreate", dependencyDelete: "dependencyDelete", rowClick: "rowClick", rowDblClick: "rowDblClick", labelWidthChange: "labelWidthChange" }, host: { properties: { "class.fly-gantt--rtl": "rtl()", "class.fly-gantt--readonly": "readonly()", "class.fly-gantt--resizing": "resizingLabels()", "attr.dir": "i18n.direction()" }, classAttribute: "fly-gantt" }, viewQueries: [{ propertyName: "bodyRef", first: true, predicate: ["bodySvg"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- Cross-scrolling canvas: a CSS grid whose header row and label column are sticky, so the\r\n time grid scrolls under a pinned header + label tree. All SVG geometry is pre-mirrored for\r\n RTL in the component (see gantt-scale mapX), so this template never branches on direction. -->\r\n<div\r\n class=\"fly-gantt__scroll\"\r\n role=\"grid\"\r\n tabindex=\"0\"\r\n [attr.aria-label]=\"'gantt.aria.grid' | translate\"\r\n [attr.aria-rowcount]=\"visibleRows().length\"\r\n (keydown)=\"onGridKeydown($event)\"\r\n>\r\n <div\r\n class=\"fly-gantt__canvas\"\r\n [style.grid-template-columns]=\"effectiveLabelWidth() + 'px ' + innerWidth() + 'px'\"\r\n [style.grid-template-rows]=\"HEADER_H + 'px ' + bodyHeight() + 'px'\"\r\n >\r\n <!-- \u2500\u2500 Corner (pinned both axes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 <div class=\"fly-gantt__corner\" [style.height.px]=\"HEADER_H\">\r\n @if (resizableLabels()) {\r\n <!-- The divider lives in the corner because the corner is the one cell pinned on BOTH\r\n axes: the grip stays reachable however far the user has scrolled. The full-height\r\n strip down the label pane below is the same gesture, presentational only. -->\r\n <div\r\n class=\"fly-gantt__splitter\"\r\n role=\"separator\"\r\n tabindex=\"0\"\r\n aria-orientation=\"vertical\"\r\n [attr.aria-label]=\"'gantt.aria.label_resize' | translate\"\r\n [attr.aria-valuenow]=\"effectiveLabelWidth()\"\r\n [attr.aria-valuemin]=\"MIN_LABEL_W\"\r\n [attr.aria-valuemax]=\"MAX_LABEL_W\"\r\n (pointerdown)=\"onLabelResizePointerDown($event)\"\r\n (keydown)=\"onLabelResizeKeydown($event)\"\r\n (dblclick)=\"resetLabelWidth()\"\r\n ></div>\r\n }\r\n </div>\r\n\r\n <!-- \u2500\u2500 Time header (pinned top) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 <svg\r\n class=\"fly-gantt__time-header\"\r\n [attr.width]=\"innerWidth()\"\r\n [attr.height]=\"HEADER_H\"\r\n [attr.viewBox]=\"'0 0 ' + innerWidth() + ' ' + HEADER_H\"\r\n aria-hidden=\"true\"\r\n >\r\n @for (b of weekendBands(); track b.key) {\r\n <rect class=\"fly-gantt__weekend\" [attr.x]=\"b.x\" [attr.y]=\"HEADER_UPPER_H\" [attr.width]=\"b.width\" [attr.height]=\"HEADER_LOWER_H\" />\r\n }\r\n @for (t of upperTicks(); track t.key) {\r\n <line class=\"fly-gantt__tick-line\" [attr.x1]=\"t.x\" [attr.y1]=\"0\" [attr.x2]=\"t.x\" [attr.y2]=\"HEADER_H\" />\r\n <text class=\"fly-gantt__tick-label fly-gantt__tick-label--upper\" [attr.x]=\"t.x + t.width / 2\" [attr.y]=\"HEADER_UPPER_H / 2 + 4\">{{ t.label }}</text>\r\n }\r\n @for (t of lowerTicks(); track t.key) {\r\n <line class=\"fly-gantt__tick-line\" [attr.x1]=\"t.x\" [attr.y1]=\"HEADER_UPPER_H\" [attr.x2]=\"t.x\" [attr.y2]=\"HEADER_H\" />\r\n <text class=\"fly-gantt__tick-label\" [attr.x]=\"t.x + t.width / 2\" [attr.y]=\"HEADER_UPPER_H + HEADER_LOWER_H / 2 + 4\">{{ t.label }}</text>\r\n }\r\n @if (todayX() !== null) {\r\n <line class=\"fly-gantt__today\" [attr.x1]=\"todayX()\" [attr.y1]=\"HEADER_UPPER_H\" [attr.x2]=\"todayX()\" [attr.y2]=\"HEADER_H\" />\r\n }\r\n </svg>\r\n\r\n <!-- \u2500\u2500 Label tree (pinned inline-start) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 <div class=\"fly-gantt__labels\" role=\"rowgroup\">\r\n @for (vm of rowVms(); track vm.flat.row.id) {\r\n <div\r\n class=\"fly-gantt__label-row\"\r\n role=\"row\"\r\n tabindex=\"-1\"\r\n [class.fly-gantt__label-row--selected]=\"selectedId() === vm.flat.row.id\"\r\n [class.fly-gantt__label-row--tinted]=\"vm.tint !== null\"\r\n [style.height.px]=\"rowHeight()\"\r\n [style.--fly-gantt-row-tint]=\"vm.tint\"\r\n [attr.aria-selected]=\"selectedId() === vm.flat.row.id\"\r\n [attr.aria-label]=\"vm.ariaLabel\"\r\n (click)=\"onRowClick(vm.flat.row.id)\"\r\n (dblclick)=\"onRowDblClick(vm.flat.row.id)\"\r\n (keydown.enter)=\"onLabelRowKeydown($event, vm.flat.row.id)\"\r\n >\r\n <span class=\"fly-gantt__label-inner\" [style.padding-inline-start.px]=\"indentFor(vm.flat.depth)\">\r\n @if (vm.flat.hasChildren) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-gantt__chevron\"\r\n [class.fly-gantt__chevron--collapsed]=\"isCollapsed(vm.flat.row.id)\"\r\n [attr.aria-label]=\"(isCollapsed(vm.flat.row.id) ? 'gantt.expand' : 'gantt.collapse') | translate\"\r\n [attr.aria-expanded]=\"!isCollapsed(vm.flat.row.id)\"\r\n (click)=\"toggleCollapse(vm.flat.row.id, $event)\"\r\n >\u25B8</button>\r\n } @else {\r\n <span class=\"fly-gantt__chevron-spacer\"></span>\r\n }\r\n @if (vm.color) {\r\n <!-- Colour chip: the row's own colour at full strength, so a milestone stays\r\n identifiable in the label pane where the band tint is deliberately faint. -->\r\n <span class=\"fly-gantt__label-swatch\" [style.background]=\"vm.color\" aria-hidden=\"true\"></span>\r\n }\r\n <span\r\n class=\"fly-gantt__label-text\"\r\n [class.fly-gantt__label-text--group]=\"vm.shape === 'group'\"\r\n [title]=\"vm.flat.row.label\"\r\n >{{ vm.flat.row.label }}</span>\r\n </span>\r\n </div>\r\n }\r\n @if (resizableLabels()) {\r\n <!-- aria-hidden so this presentational twin of the corner separator is not a second\r\n (and structurally invalid) child of the rowgroup in the accessibility tree. -->\r\n <div\r\n class=\"fly-gantt__splitter fly-gantt__splitter--rail\"\r\n aria-hidden=\"true\"\r\n (pointerdown)=\"onLabelResizePointerDown($event)\"\r\n (dblclick)=\"resetLabelWidth()\"\r\n ></div>\r\n }\r\n </div>\r\n\r\n <!-- \u2500\u2500 Time grid body \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 <svg\r\n #bodySvg\r\n class=\"fly-gantt__body\"\r\n [attr.width]=\"innerWidth()\"\r\n [attr.height]=\"bodyHeight()\"\r\n [attr.viewBox]=\"'0 0 ' + innerWidth() + ' ' + bodyHeight()\"\r\n >\r\n <defs>\r\n <marker\r\n [attr.id]=\"markerId\"\r\n markerWidth=\"8\"\r\n markerHeight=\"8\"\r\n refX=\"6\"\r\n refY=\"4\"\r\n orient=\"auto\"\r\n markerUnits=\"userSpaceOnUse\"\r\n >\r\n <path class=\"fly-gantt__arrowhead\" d=\"M0,0 L7,4 L0,8 Z\" />\r\n </marker>\r\n </defs>\r\n\r\n <!-- Row colour bands sit at the very bottom of the stack, so the weekend shading and the\r\n gridlines below still read through them. -->\r\n @for (vm of tintedRows(); track vm.flat.row.id) {\r\n <rect\r\n class=\"fly-gantt__row-band\"\r\n [attr.x]=\"0\"\r\n [attr.y]=\"vm.y\"\r\n [attr.width]=\"innerWidth()\"\r\n [attr.height]=\"rowHeight()\"\r\n [style.fill]=\"vm.tint\"\r\n />\r\n }\r\n\r\n <!-- Weekend shading + vertical gridlines (behind the bars). -->\r\n @for (b of weekendBands(); track b.key) {\r\n <rect class=\"fly-gantt__weekend\" [attr.x]=\"b.x\" [attr.y]=\"0\" [attr.width]=\"b.width\" [attr.height]=\"bodyHeight()\" />\r\n }\r\n @for (t of lowerTicks(); track t.key) {\r\n <line class=\"fly-gantt__grid-line\" [attr.x1]=\"t.x\" [attr.y1]=\"0\" [attr.x2]=\"t.x\" [attr.y2]=\"bodyHeight()\" />\r\n }\r\n\r\n <!-- Rows: a full-width hit rect (click + link drop target) then the shape. -->\r\n @for (vm of rowVms(); track vm.flat.row.id) {\r\n <g class=\"fly-gantt__row\" [attr.data-row-id]=\"vm.flat.row.id\">\r\n <rect\r\n class=\"fly-gantt__row-hit\"\r\n [class.fly-gantt__row-hit--selected]=\"selectedId() === vm.flat.row.id\"\r\n [attr.x]=\"0\"\r\n [attr.y]=\"vm.y\"\r\n [attr.width]=\"innerWidth()\"\r\n [attr.height]=\"rowHeight()\"\r\n (pointerdown)=\"onRowClick(vm.flat.row.id)\"\r\n (dblclick)=\"onRowDblClick(vm.flat.row.id)\"\r\n />\r\n\r\n @switch (vm.shape) {\r\n @case ('group') {\r\n <path class=\"fly-gantt__group\" [attr.d]=\"groupPath(vm)\" [style.fill]=\"vm.color\" [style.stroke]=\"vm.color\" />\r\n }\r\n @case ('milestone') {\r\n <polygon\r\n class=\"fly-gantt__milestone\"\r\n [class.fly-gantt__milestone--editable]=\"vm.editable\"\r\n [attr.points]=\"diamondPoints(vm)\"\r\n [style.fill]=\"vm.color\"\r\n (pointerdown)=\"onBarPointerDown($event, vm, 'move')\"\r\n />\r\n @if (!readonly() && !vm.flat.row.readonly) {\r\n <!-- A diamond has no width, so one connector serves it; it reads as the finish\r\n end, which is what makes a milestone\u2192task drag the FS everyone expects. -->\r\n <circle\r\n class=\"fly-gantt__connector\"\r\n [attr.cx]=\"milestoneConnectorX(vm)\"\r\n [attr.cy]=\"vm.midY\"\r\n r=\"4\"\r\n [attr.aria-label]=\"'gantt.aria.link_handle' | translate: { label: vm.flat.row.label }\"\r\n (pointerdown)=\"onLinkPointerDown($event, vm, 'finish')\"\r\n />\r\n }\r\n }\r\n @case ('bar') {\r\n <g class=\"fly-gantt__bar-group\">\r\n <rect\r\n class=\"fly-gantt__bar\"\r\n [class.fly-gantt__bar--editable]=\"vm.editable\"\r\n [attr.x]=\"vm.x\"\r\n [attr.y]=\"barTop(vm)\"\r\n [attr.width]=\"vm.width\"\r\n [attr.height]=\"barHeight()\"\r\n rx=\"4\"\r\n [style.fill]=\"vm.color\"\r\n (pointerdown)=\"onBarPointerDown($event, vm, 'move')\"\r\n />\r\n @if (vm.progressWidth > 0) {\r\n <rect\r\n class=\"fly-gantt__progress\"\r\n [attr.x]=\"vm.x\"\r\n [attr.y]=\"barTop(vm)\"\r\n [attr.width]=\"vm.progressWidth\"\r\n [attr.height]=\"barHeight()\"\r\n rx=\"4\"\r\n />\r\n }\r\n @if (vm.editable) {\r\n <!-- Resize handles at both edges. -->\r\n <rect\r\n class=\"fly-gantt__handle\"\r\n [attr.x]=\"vm.x\"\r\n [attr.y]=\"barTop(vm)\"\r\n [attr.width]=\"HANDLE_W\"\r\n [attr.height]=\"barHeight()\"\r\n [attr.aria-label]=\"'gantt.aria.resize_start' | translate\"\r\n (pointerdown)=\"onBarPointerDown($event, vm, 'resize-start')\"\r\n />\r\n <rect\r\n class=\"fly-gantt__handle\"\r\n [attr.x]=\"vm.x + vm.width - HANDLE_W\"\r\n [attr.y]=\"barTop(vm)\"\r\n [attr.width]=\"HANDLE_W\"\r\n [attr.height]=\"barHeight()\"\r\n [attr.aria-label]=\"'gantt.aria.resize_end' | translate\"\r\n (pointerdown)=\"onBarPointerDown($event, vm, 'resize-end')\"\r\n />\r\n }\r\n @if (!readonly() && !vm.flat.row.readonly) {\r\n <!-- One connector per END. Dragging from the start edge yields an SS/SF link\r\n and from the finish edge an FS/FF one \u2014 the drop edge picks which. -->\r\n <circle\r\n class=\"fly-gantt__connector\"\r\n [attr.cx]=\"vm.startX\"\r\n [attr.cy]=\"vm.midY\"\r\n r=\"4\"\r\n [attr.aria-label]=\"'gantt.aria.link_handle_start' | translate: { label: vm.flat.row.label }\"\r\n (pointerdown)=\"onLinkPointerDown($event, vm, 'start')\"\r\n />\r\n <circle\r\n class=\"fly-gantt__connector\"\r\n [attr.cx]=\"vm.endX\"\r\n [attr.cy]=\"vm.midY\"\r\n r=\"4\"\r\n [attr.aria-label]=\"'gantt.aria.link_handle' | translate: { label: vm.flat.row.label }\"\r\n (pointerdown)=\"onLinkPointerDown($event, vm, 'finish')\"\r\n />\r\n }\r\n </g>\r\n }\r\n }\r\n </g>\r\n }\r\n\r\n <!-- Dependency arrows (skipped for missing / collapsed endpoints). Each carries a fat\r\n transparent twin so a 1.5px line is still clickable at pointer precision. -->\r\n @for (link of linkVms(); track link.key) {\r\n <g class=\"fly-gantt__link-group\" [class.fly-gantt__link-group--selected]=\"selectedLinkKey() === link.key\">\r\n <path\r\n class=\"fly-gantt__link-hit\"\r\n [attr.d]=\"link.path\"\r\n [attr.aria-label]=\"link.ariaLabel\"\r\n (click)=\"onLinkClick($event, link)\"\r\n />\r\n <path class=\"fly-gantt__link\" [attr.d]=\"link.path\" [attr.marker-end]=\"'url(#' + markerId + ')'\" />\r\n @if (selectedLinkKey() === link.key && !readonly()) {\r\n <g\r\n class=\"fly-gantt__link-delete\"\r\n [attr.transform]=\"'translate(' + link.badgeX + ',' + link.badgeY + ')'\"\r\n [attr.aria-label]=\"'gantt.aria.delete_link' | translate\"\r\n (click)=\"deleteLink($event, link)\"\r\n >\r\n <circle class=\"fly-gantt__link-delete-bg\" r=\"8\" />\r\n <path class=\"fly-gantt__link-delete-x\" d=\"M-3.5,-3.5 L3.5,3.5 M3.5,-3.5 L-3.5,3.5\" />\r\n </g>\r\n }\r\n </g>\r\n }\r\n\r\n <!-- In-flight link rubber band. -->\r\n @if (linkGesturePath(); as gp) {\r\n <path class=\"fly-gantt__link fly-gantt__link--ghost\" [attr.d]=\"gp\" [attr.marker-end]=\"'url(#' + markerId + ')'\" />\r\n }\r\n\r\n <!-- Today line (over the bars). -->\r\n @if (todayX() !== null) {\r\n <line class=\"fly-gantt__today\" [attr.x1]=\"todayX()\" [attr.y1]=\"0\" [attr.x2]=\"todayX()\" [attr.y2]=\"bodyHeight()\" />\r\n }\r\n </svg>\r\n </div>\r\n\r\n @if (visibleRows().length === 0) {\r\n <p class=\"fly-gantt__empty\">{{ 'gantt.no_data' | translate }}</p>\r\n }\r\n @if (overflowCount() > 0) {\r\n <p class=\"fly-gantt__overflow\">\r\n {{ 'gantt.overflow' | translate: { shown: maxRows(), total: maxRows() + overflowCount() } }}\r\n </p>\r\n }\r\n</div>\r\n\r\n<!-- Drag candidate tooltip (HTML overlay, follows the cursor). -->\r\n@if (dragTooltip(); as tip) {\r\n <div class=\"fly-gantt__tooltip\" [style.left.px]=\"tip.x\" [style.top.px]=\"tip.y\">{{ tip.text }}</div>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--bg-2, var(--surface-card, #fff));--_surface-alt: var(--bg-3, var(--glass-bg-elevated, #f8fafc));--_surface-hover: var(--bg-hover, var(--surface-hover, #f1f5f9));--_border: var(--w08, var(--surface-border, #e2e8f0));--_border-strong: var(--w1, var(--surface-border, #cbd5e1));--_grid-line: var(--line-3, var(--surface-border, #eef2f7));--_text: var(--ink, var(--text-color, #0f172a));--_text-subtle: var(--ink-3, var(--text-color-secondary, #64748b));--_text-faint: var(--ink-4, var(--text-color-secondary, #94a3b8));--_accent: var(--accent);--_bar: var(--_accent);--_bar-progress: color-mix(in srgb, var(--_accent) 72%, #000);--_weekend: color-mix(in srgb, var(--_text-subtle) 9%, transparent);--_today: var(--danger, #ef4444);--_link: var(--_text-subtle);--_radius: var(--r-lg, 8px);--_transition: var(--t-state, .12s ease);display:block;inline-size:100%;block-size:100%;min-block-size:200px;color:var(--_text);font-size:13px}:host(.fly-gantt--resizing){cursor:col-resize;-webkit-user-select:none;user-select:none}.fly-gantt__scroll{position:relative;inline-size:100%;block-size:100%;overflow:auto;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);box-shadow:var(--shadow-card, none);outline:none}.fly-gantt__scroll:focus-visible{outline:2px solid var(--_accent);outline-offset:-2px}.fly-gantt__canvas{display:grid;position:relative}.fly-gantt__corner{position:sticky;inset-block-start:0;inset-inline-start:0;z-index:4;background:var(--_surface-alt);-webkit-backdrop-filter:blur(12px) saturate(140%);backdrop-filter:blur(12px) saturate(140%);border-inline-end:1px solid var(--_border);border-block-end:1px solid var(--_border-strong)}.fly-gantt__time-header{position:sticky;inset-block-start:0;z-index:3;display:block;background:var(--_surface-alt);-webkit-backdrop-filter:blur(12px) saturate(140%);backdrop-filter:blur(12px) saturate(140%);border-block-end:1px solid var(--_border-strong)}.fly-gantt__tick-line{stroke:var(--_grid-line);stroke-width:1}.fly-gantt__tick-label{fill:var(--_text-subtle);font-size:11px;text-anchor:middle;dominant-baseline:middle}.fly-gantt__tick-label--upper{fill:var(--_text);font-weight:600}.fly-gantt__splitter{position:absolute;inset-block:0;inset-inline-end:-3px;inline-size:7px;z-index:5;cursor:col-resize;touch-action:none}.fly-gantt__splitter:after{content:\"\";position:absolute;inset-block:0;inset-inline-start:3px;inline-size:1px;background:transparent;transition:background var(--_transition)}.fly-gantt__splitter:hover:after,.fly-gantt__splitter:focus-visible:after{background:var(--_accent)}.fly-gantt__splitter:focus-visible{outline:2px solid var(--_accent);outline-offset:-1px}:host(.fly-gantt--resizing) .fly-gantt__splitter:after{background:var(--_accent)}.fly-gantt__labels{position:sticky;inset-inline-start:0;z-index:2;background:var(--_surface);border-inline-end:1px solid var(--_border)}.fly-gantt__label-row{position:relative;display:flex;align-items:center;border-block-end:1px solid var(--_grid-line);cursor:pointer;transition:background var(--_transition)}.fly-gantt__label-row:hover{background:var(--_surface-hover)}.fly-gantt__label-row--selected{background:var(--_surface-hover);box-shadow:inset 3px 0 0 0 var(--_accent)}.fly-gantt__label-row--tinted:before{content:\"\";position:absolute;inset:0;background:var(--fly-gantt-row-tint);pointer-events:none}.fly-gantt__label-inner{position:relative;display:flex;align-items:center;gap:4px;inline-size:100%;min-inline-size:0;padding-inline-end:8px}.fly-gantt__label-swatch{flex:0 0 auto;inline-size:8px;block-size:8px;border-radius:2px}.fly-gantt__chevron{flex:0 0 auto;inline-size:18px;block-size:18px;padding:0;border:none;background:transparent;color:var(--_text-subtle);cursor:pointer;line-height:1;transform:rotate(90deg);transition:transform var(--_transition)}.fly-gantt__chevron--collapsed{transform:rotate(0)}:host(.fly-gantt--rtl) .fly-gantt__chevron--collapsed{transform:rotate(180deg)}.fly-gantt__chevron:focus-visible{outline:2px solid var(--_accent);outline-offset:1px;border-radius:4px}.fly-gantt__chevron-spacer{flex:0 0 auto;inline-size:18px}.fly-gantt__label-text{min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-gantt__label-text--group{font-weight:600}.fly-gantt__body{display:block;touch-action:pan-x pan-y}.fly-gantt__row-band{pointer-events:none}.fly-gantt__weekend{fill:var(--_weekend)}.fly-gantt__grid-line{stroke:var(--_grid-line);stroke-width:1}.fly-gantt__row-hit{fill:transparent}.fly-gantt__row-hit--selected{fill:var(--_surface-hover)}.fly-gantt__bar{fill:var(--_bar);stroke:none}.fly-gantt__bar--editable{cursor:grab}.fly-gantt__progress{fill:var(--_bar-progress);pointer-events:none}.fly-gantt__handle{fill:transparent;cursor:ew-resize}.fly-gantt__connector{fill:var(--_surface);stroke:var(--_accent);stroke-width:1.5;cursor:crosshair;opacity:0;transition:opacity var(--_transition)}.fly-gantt__row:hover .fly-gantt__connector{opacity:1}.fly-gantt__milestone{fill:var(--_accent);stroke:var(--_surface);stroke-width:1}.fly-gantt__milestone--editable{cursor:grab}.fly-gantt__group{fill:var(--_text-subtle);stroke:var(--_text-subtle)}.fly-gantt__link{fill:none;stroke:var(--_link);stroke-width:1.5;pointer-events:none}.fly-gantt__link-hit{fill:none;stroke:transparent;stroke-width:11;pointer-events:stroke;cursor:pointer}.fly-gantt__link-group:hover .fly-gantt__link{stroke:var(--_text)}.fly-gantt__link-group--selected .fly-gantt__link{stroke:var(--_accent);stroke-width:2.5}.fly-gantt__link-delete-bg{fill:var(--_accent)}.fly-gantt__link-delete{cursor:pointer}.fly-gantt__link-delete-x{stroke:var(--_surface);stroke-width:1.75;stroke-linecap:round;fill:none;pointer-events:none}.fly-gantt__link--ghost{stroke-dasharray:4 3;opacity:.8}.fly-gantt__arrowhead{fill:var(--_link)}.fly-gantt__today{stroke:var(--_today);stroke-width:1.5;stroke-dasharray:3 3;pointer-events:none}.fly-gantt__empty,.fly-gantt__overflow{margin:0;padding:12px;color:var(--_text-faint);font-size:12px;text-align:center}.fly-gantt__tooltip{position:fixed;z-index:20;transform:translate(12px,16px);padding:4px 8px;border-radius:6px;background:var(--_text);color:var(--_surface);font-size:12px;white-space:nowrap;pointer-events:none;direction:ltr;unicode-bidi:isolate}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
13546
13568
  }
13547
13569
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyGanttComponent, decorators: [{
13548
13570
  type: Component,
@@ -13552,7 +13574,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
13552
13574
  '[class.fly-gantt--readonly]': 'readonly()',
13553
13575
  '[class.fly-gantt--resizing]': 'resizingLabels()',
13554
13576
  '[attr.dir]': 'i18n.direction()',
13555
- }, template: "<!-- Cross-scrolling canvas: a CSS grid whose header row and label column are sticky, so the\n time grid scrolls under a pinned header + label tree. All SVG geometry is pre-mirrored for\n RTL in the component (see gantt-scale mapX), so this template never branches on direction. -->\n<div\n class=\"fly-gantt__scroll\"\n role=\"grid\"\n tabindex=\"0\"\n [attr.aria-label]=\"'gantt.aria.grid' | translate\"\n [attr.aria-rowcount]=\"visibleRows().length\"\n (keydown)=\"onGridKeydown($event)\"\n>\n <div\n class=\"fly-gantt__canvas\"\n [style.grid-template-columns]=\"effectiveLabelWidth() + 'px ' + innerWidth() + 'px'\"\n [style.grid-template-rows]=\"HEADER_H + 'px ' + bodyHeight() + 'px'\"\n >\n <!-- \u2500\u2500 Corner (pinned both axes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"fly-gantt__corner\" [style.height.px]=\"HEADER_H\">\n @if (resizableLabels()) {\n <!-- The divider lives in the corner because the corner is the one cell pinned on BOTH\n axes: the grip stays reachable however far the user has scrolled. The full-height\n strip down the label pane below is the same gesture, presentational only. -->\n <div\n class=\"fly-gantt__splitter\"\n role=\"separator\"\n tabindex=\"0\"\n aria-orientation=\"vertical\"\n [attr.aria-label]=\"'gantt.aria.label_resize' | translate\"\n [attr.aria-valuenow]=\"effectiveLabelWidth()\"\n [attr.aria-valuemin]=\"MIN_LABEL_W\"\n [attr.aria-valuemax]=\"MAX_LABEL_W\"\n (pointerdown)=\"onLabelResizePointerDown($event)\"\n (keydown)=\"onLabelResizeKeydown($event)\"\n (dblclick)=\"resetLabelWidth()\"\n ></div>\n }\n </div>\n\n <!-- \u2500\u2500 Time header (pinned top) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <svg\n class=\"fly-gantt__time-header\"\n [attr.width]=\"innerWidth()\"\n [attr.height]=\"HEADER_H\"\n [attr.viewBox]=\"'0 0 ' + innerWidth() + ' ' + HEADER_H\"\n aria-hidden=\"true\"\n >\n @for (b of weekendBands(); track b.key) {\n <rect class=\"fly-gantt__weekend\" [attr.x]=\"b.x\" [attr.y]=\"HEADER_UPPER_H\" [attr.width]=\"b.width\" [attr.height]=\"HEADER_LOWER_H\" />\n }\n @for (t of upperTicks(); track t.key) {\n <line class=\"fly-gantt__tick-line\" [attr.x1]=\"t.x\" [attr.y1]=\"0\" [attr.x2]=\"t.x\" [attr.y2]=\"HEADER_H\" />\n <text class=\"fly-gantt__tick-label fly-gantt__tick-label--upper\" [attr.x]=\"t.x + t.width / 2\" [attr.y]=\"HEADER_UPPER_H / 2 + 4\">{{ t.label }}</text>\n }\n @for (t of lowerTicks(); track t.key) {\n <line class=\"fly-gantt__tick-line\" [attr.x1]=\"t.x\" [attr.y1]=\"HEADER_UPPER_H\" [attr.x2]=\"t.x\" [attr.y2]=\"HEADER_H\" />\n <text class=\"fly-gantt__tick-label\" [attr.x]=\"t.x + t.width / 2\" [attr.y]=\"HEADER_UPPER_H + HEADER_LOWER_H / 2 + 4\">{{ t.label }}</text>\n }\n @if (todayX() !== null) {\n <line class=\"fly-gantt__today\" [attr.x1]=\"todayX()\" [attr.y1]=\"HEADER_UPPER_H\" [attr.x2]=\"todayX()\" [attr.y2]=\"HEADER_H\" />\n }\n </svg>\n\n <!-- \u2500\u2500 Label tree (pinned inline-start) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"fly-gantt__labels\" role=\"rowgroup\">\n @for (vm of rowVms(); track vm.flat.row.id) {\n <div\n class=\"fly-gantt__label-row\"\n role=\"row\"\n tabindex=\"-1\"\n [class.fly-gantt__label-row--selected]=\"selectedId() === vm.flat.row.id\"\n [class.fly-gantt__label-row--tinted]=\"vm.tint !== null\"\n [style.height.px]=\"rowHeight()\"\n [style.--fly-gantt-row-tint]=\"vm.tint\"\n [attr.aria-selected]=\"selectedId() === vm.flat.row.id\"\n [attr.aria-label]=\"vm.ariaLabel\"\n (click)=\"onRowClick(vm.flat.row.id)\"\n (dblclick)=\"onRowDblClick(vm.flat.row.id)\"\n (keydown.enter)=\"onLabelRowKeydown($event, vm.flat.row.id)\"\n >\n <span class=\"fly-gantt__label-inner\" [style.padding-inline-start.px]=\"indentFor(vm.flat.depth)\">\n @if (vm.flat.hasChildren) {\n <button\n type=\"button\"\n class=\"fly-gantt__chevron\"\n [class.fly-gantt__chevron--collapsed]=\"isCollapsed(vm.flat.row.id)\"\n [attr.aria-label]=\"(isCollapsed(vm.flat.row.id) ? 'gantt.expand' : 'gantt.collapse') | translate\"\n [attr.aria-expanded]=\"!isCollapsed(vm.flat.row.id)\"\n (click)=\"toggleCollapse(vm.flat.row.id, $event)\"\n >\u25B8</button>\n } @else {\n <span class=\"fly-gantt__chevron-spacer\"></span>\n }\n @if (vm.color) {\n <!-- Colour chip: the row's own colour at full strength, so a milestone stays\n identifiable in the label pane where the band tint is deliberately faint. -->\n <span class=\"fly-gantt__label-swatch\" [style.background]=\"vm.color\" aria-hidden=\"true\"></span>\n }\n <span\n class=\"fly-gantt__label-text\"\n [class.fly-gantt__label-text--group]=\"vm.shape === 'group'\"\n [title]=\"vm.flat.row.label\"\n >{{ vm.flat.row.label }}</span>\n </span>\n </div>\n }\n @if (resizableLabels()) {\n <!-- aria-hidden so this presentational twin of the corner separator is not a second\n (and structurally invalid) child of the rowgroup in the accessibility tree. -->\n <div\n class=\"fly-gantt__splitter fly-gantt__splitter--rail\"\n aria-hidden=\"true\"\n (pointerdown)=\"onLabelResizePointerDown($event)\"\n (dblclick)=\"resetLabelWidth()\"\n ></div>\n }\n </div>\n\n <!-- \u2500\u2500 Time grid body \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <svg\n #bodySvg\n class=\"fly-gantt__body\"\n [attr.width]=\"innerWidth()\"\n [attr.height]=\"bodyHeight()\"\n [attr.viewBox]=\"'0 0 ' + innerWidth() + ' ' + bodyHeight()\"\n >\n <defs>\n <marker\n [attr.id]=\"markerId\"\n markerWidth=\"8\"\n markerHeight=\"8\"\n refX=\"6\"\n refY=\"4\"\n orient=\"auto\"\n markerUnits=\"userSpaceOnUse\"\n >\n <path class=\"fly-gantt__arrowhead\" d=\"M0,0 L7,4 L0,8 Z\" />\n </marker>\n </defs>\n\n <!-- Row colour bands sit at the very bottom of the stack, so the weekend shading and the\n gridlines below still read through them. -->\n @for (vm of tintedRows(); track vm.flat.row.id) {\n <rect\n class=\"fly-gantt__row-band\"\n [attr.x]=\"0\"\n [attr.y]=\"vm.y\"\n [attr.width]=\"innerWidth()\"\n [attr.height]=\"rowHeight()\"\n [style.fill]=\"vm.tint\"\n />\n }\n\n <!-- Weekend shading + vertical gridlines (behind the bars). -->\n @for (b of weekendBands(); track b.key) {\n <rect class=\"fly-gantt__weekend\" [attr.x]=\"b.x\" [attr.y]=\"0\" [attr.width]=\"b.width\" [attr.height]=\"bodyHeight()\" />\n }\n @for (t of lowerTicks(); track t.key) {\n <line class=\"fly-gantt__grid-line\" [attr.x1]=\"t.x\" [attr.y1]=\"0\" [attr.x2]=\"t.x\" [attr.y2]=\"bodyHeight()\" />\n }\n\n <!-- Rows: a full-width hit rect (click + link drop target) then the shape. -->\n @for (vm of rowVms(); track vm.flat.row.id) {\n <g class=\"fly-gantt__row\" [attr.data-row-id]=\"vm.flat.row.id\">\n <rect\n class=\"fly-gantt__row-hit\"\n [class.fly-gantt__row-hit--selected]=\"selectedId() === vm.flat.row.id\"\n [attr.x]=\"0\"\n [attr.y]=\"vm.y\"\n [attr.width]=\"innerWidth()\"\n [attr.height]=\"rowHeight()\"\n (pointerdown)=\"onRowClick(vm.flat.row.id)\"\n (dblclick)=\"onRowDblClick(vm.flat.row.id)\"\n />\n\n @switch (vm.shape) {\n @case ('group') {\n <path class=\"fly-gantt__group\" [attr.d]=\"groupPath(vm)\" [style.fill]=\"vm.color\" [style.stroke]=\"vm.color\" />\n }\n @case ('milestone') {\n <polygon\n class=\"fly-gantt__milestone\"\n [class.fly-gantt__milestone--editable]=\"vm.editable\"\n [attr.points]=\"diamondPoints(vm)\"\n [style.fill]=\"vm.color\"\n (pointerdown)=\"onBarPointerDown($event, vm, 'move')\"\n />\n @if (!readonly() && !vm.flat.row.readonly) {\n <!-- A diamond has no width, so one connector serves it; it reads as the finish\n end, which is what makes a milestone\u2192task drag the FS everyone expects. -->\n <circle\n class=\"fly-gantt__connector\"\n [attr.cx]=\"milestoneConnectorX(vm)\"\n [attr.cy]=\"vm.midY\"\n r=\"4\"\n [attr.aria-label]=\"'gantt.aria.link_handle' | translate: { label: vm.flat.row.label }\"\n (pointerdown)=\"onLinkPointerDown($event, vm, 'finish')\"\n />\n }\n }\n @case ('bar') {\n <g class=\"fly-gantt__bar-group\">\n <rect\n class=\"fly-gantt__bar\"\n [class.fly-gantt__bar--editable]=\"vm.editable\"\n [attr.x]=\"vm.x\"\n [attr.y]=\"barTop(vm)\"\n [attr.width]=\"vm.width\"\n [attr.height]=\"barHeight()\"\n rx=\"4\"\n [style.fill]=\"vm.color\"\n (pointerdown)=\"onBarPointerDown($event, vm, 'move')\"\n />\n @if (vm.progressWidth > 0) {\n <rect\n class=\"fly-gantt__progress\"\n [attr.x]=\"vm.x\"\n [attr.y]=\"barTop(vm)\"\n [attr.width]=\"vm.progressWidth\"\n [attr.height]=\"barHeight()\"\n rx=\"4\"\n />\n }\n @if (vm.editable) {\n <!-- Resize handles at both edges. -->\n <rect\n class=\"fly-gantt__handle\"\n [attr.x]=\"vm.x\"\n [attr.y]=\"barTop(vm)\"\n [attr.width]=\"HANDLE_W\"\n [attr.height]=\"barHeight()\"\n [attr.aria-label]=\"'gantt.aria.resize_start' | translate\"\n (pointerdown)=\"onBarPointerDown($event, vm, 'resize-start')\"\n />\n <rect\n class=\"fly-gantt__handle\"\n [attr.x]=\"vm.x + vm.width - HANDLE_W\"\n [attr.y]=\"barTop(vm)\"\n [attr.width]=\"HANDLE_W\"\n [attr.height]=\"barHeight()\"\n [attr.aria-label]=\"'gantt.aria.resize_end' | translate\"\n (pointerdown)=\"onBarPointerDown($event, vm, 'resize-end')\"\n />\n }\n @if (!readonly() && !vm.flat.row.readonly) {\n <!-- One connector per END. Dragging from the start edge yields an SS/SF link\n and from the finish edge an FS/FF one \u2014 the drop edge picks which. -->\n <circle\n class=\"fly-gantt__connector\"\n [attr.cx]=\"vm.startX\"\n [attr.cy]=\"vm.midY\"\n r=\"4\"\n [attr.aria-label]=\"'gantt.aria.link_handle_start' | translate: { label: vm.flat.row.label }\"\n (pointerdown)=\"onLinkPointerDown($event, vm, 'start')\"\n />\n <circle\n class=\"fly-gantt__connector\"\n [attr.cx]=\"vm.endX\"\n [attr.cy]=\"vm.midY\"\n r=\"4\"\n [attr.aria-label]=\"'gantt.aria.link_handle' | translate: { label: vm.flat.row.label }\"\n (pointerdown)=\"onLinkPointerDown($event, vm, 'finish')\"\n />\n }\n </g>\n }\n }\n </g>\n }\n\n <!-- Dependency arrows (skipped for missing / collapsed endpoints). Each carries a fat\n transparent twin so a 1.5px line is still clickable at pointer precision. -->\n @for (link of linkVms(); track link.key) {\n <g class=\"fly-gantt__link-group\" [class.fly-gantt__link-group--selected]=\"selectedLinkKey() === link.key\">\n <path\n class=\"fly-gantt__link-hit\"\n [attr.d]=\"link.path\"\n [attr.aria-label]=\"link.ariaLabel\"\n (click)=\"onLinkClick($event, link)\"\n />\n <path class=\"fly-gantt__link\" [attr.d]=\"link.path\" [attr.marker-end]=\"'url(#' + markerId + ')'\" />\n @if (selectedLinkKey() === link.key && !readonly()) {\n <g\n class=\"fly-gantt__link-delete\"\n [attr.transform]=\"'translate(' + link.badgeX + ',' + link.badgeY + ')'\"\n [attr.aria-label]=\"'gantt.aria.delete_link' | translate\"\n (click)=\"deleteLink($event, link)\"\n >\n <circle class=\"fly-gantt__link-delete-bg\" r=\"8\" />\n <path class=\"fly-gantt__link-delete-x\" d=\"M-3.5,-3.5 L3.5,3.5 M3.5,-3.5 L-3.5,3.5\" />\n </g>\n }\n </g>\n }\n\n <!-- In-flight link rubber band. -->\n @if (linkGesturePath(); as gp) {\n <path class=\"fly-gantt__link fly-gantt__link--ghost\" [attr.d]=\"gp\" [attr.marker-end]=\"'url(#' + markerId + ')'\" />\n }\n\n <!-- Today line (over the bars). -->\n @if (todayX() !== null) {\n <line class=\"fly-gantt__today\" [attr.x1]=\"todayX()\" [attr.y1]=\"0\" [attr.x2]=\"todayX()\" [attr.y2]=\"bodyHeight()\" />\n }\n </svg>\n </div>\n\n @if (visibleRows().length === 0) {\n <p class=\"fly-gantt__empty\">{{ 'gantt.no_data' | translate }}</p>\n }\n @if (overflowCount() > 0) {\n <p class=\"fly-gantt__overflow\">\n {{ 'gantt.overflow' | translate: { shown: maxRows(), total: maxRows() + overflowCount() } }}\n </p>\n }\n</div>\n\n<!-- Drag candidate tooltip (HTML overlay, follows the cursor). -->\n@if (dragTooltip(); as tip) {\n <div class=\"fly-gantt__tooltip\" [style.left.px]=\"tip.x\" [style.top.px]=\"tip.y\">{{ tip.text }}</div>\n}\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--bg-2, var(--surface-card, #fff));--_surface-alt: var(--bg-3, var(--glass-bg-elevated, #f8fafc));--_surface-hover: var(--bg-hover, var(--surface-hover, #f1f5f9));--_border: var(--w08, var(--surface-border, #e2e8f0));--_border-strong: var(--w1, var(--surface-border, #cbd5e1));--_grid-line: var(--line-3, var(--surface-border, #eef2f7));--_text: var(--ink, var(--text-color, #0f172a));--_text-subtle: var(--ink-3, var(--text-color-secondary, #64748b));--_text-faint: var(--ink-4, var(--text-color-secondary, #94a3b8));--_accent: var(--accent);--_bar: var(--_accent);--_bar-progress: color-mix(in srgb, var(--_accent) 72%, #000);--_weekend: color-mix(in srgb, var(--_text-subtle) 9%, transparent);--_today: var(--danger, #ef4444);--_link: var(--_text-subtle);--_radius: var(--r-lg, 8px);--_transition: var(--t-state, .12s ease);display:block;inline-size:100%;block-size:100%;min-block-size:200px;color:var(--_text);font-size:13px}:host(.fly-gantt--resizing){cursor:col-resize;-webkit-user-select:none;user-select:none}.fly-gantt__scroll{position:relative;inline-size:100%;block-size:100%;overflow:auto;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);box-shadow:var(--shadow-card, none);outline:none}.fly-gantt__scroll:focus-visible{outline:2px solid var(--_accent);outline-offset:-2px}.fly-gantt__canvas{display:grid;position:relative}.fly-gantt__corner{position:sticky;inset-block-start:0;inset-inline-start:0;z-index:4;background:var(--_surface-alt);-webkit-backdrop-filter:blur(12px) saturate(140%);backdrop-filter:blur(12px) saturate(140%);border-inline-end:1px solid var(--_border);border-block-end:1px solid var(--_border-strong)}.fly-gantt__time-header{position:sticky;inset-block-start:0;z-index:3;display:block;background:var(--_surface-alt);-webkit-backdrop-filter:blur(12px) saturate(140%);backdrop-filter:blur(12px) saturate(140%);border-block-end:1px solid var(--_border-strong)}.fly-gantt__tick-line{stroke:var(--_grid-line);stroke-width:1}.fly-gantt__tick-label{fill:var(--_text-subtle);font-size:11px;text-anchor:middle;dominant-baseline:middle}.fly-gantt__tick-label--upper{fill:var(--_text);font-weight:600}.fly-gantt__splitter{position:absolute;inset-block:0;inset-inline-end:-3px;inline-size:7px;z-index:5;cursor:col-resize;touch-action:none}.fly-gantt__splitter:after{content:\"\";position:absolute;inset-block:0;inset-inline-start:3px;inline-size:1px;background:transparent;transition:background var(--_transition)}.fly-gantt__splitter:hover:after,.fly-gantt__splitter:focus-visible:after{background:var(--_accent)}.fly-gantt__splitter:focus-visible{outline:2px solid var(--_accent);outline-offset:-1px}:host(.fly-gantt--resizing) .fly-gantt__splitter:after{background:var(--_accent)}.fly-gantt__labels{position:sticky;inset-inline-start:0;z-index:2;background:var(--_surface);border-inline-end:1px solid var(--_border)}.fly-gantt__label-row{position:relative;display:flex;align-items:center;border-block-end:1px solid var(--_grid-line);cursor:pointer;transition:background var(--_transition)}.fly-gantt__label-row:hover{background:var(--_surface-hover)}.fly-gantt__label-row--selected{background:var(--_surface-hover);box-shadow:inset 3px 0 0 0 var(--_accent)}.fly-gantt__label-row--tinted:before{content:\"\";position:absolute;inset:0;background:var(--fly-gantt-row-tint);pointer-events:none}.fly-gantt__label-inner{position:relative;display:flex;align-items:center;gap:4px;inline-size:100%;min-inline-size:0;padding-inline-end:8px}.fly-gantt__label-swatch{flex:0 0 auto;inline-size:8px;block-size:8px;border-radius:2px}.fly-gantt__chevron{flex:0 0 auto;inline-size:18px;block-size:18px;padding:0;border:none;background:transparent;color:var(--_text-subtle);cursor:pointer;line-height:1;transform:rotate(90deg);transition:transform var(--_transition)}.fly-gantt__chevron--collapsed{transform:rotate(0)}:host(.fly-gantt--rtl) .fly-gantt__chevron--collapsed{transform:rotate(180deg)}.fly-gantt__chevron:focus-visible{outline:2px solid var(--_accent);outline-offset:1px;border-radius:4px}.fly-gantt__chevron-spacer{flex:0 0 auto;inline-size:18px}.fly-gantt__label-text{min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-gantt__label-text--group{font-weight:600}.fly-gantt__body{display:block;touch-action:pan-x pan-y}.fly-gantt__row-band{pointer-events:none}.fly-gantt__weekend{fill:var(--_weekend)}.fly-gantt__grid-line{stroke:var(--_grid-line);stroke-width:1}.fly-gantt__row-hit{fill:transparent}.fly-gantt__row-hit--selected{fill:var(--_surface-hover)}.fly-gantt__bar{fill:var(--_bar);stroke:none}.fly-gantt__bar--editable{cursor:grab}.fly-gantt__progress{fill:var(--_bar-progress);pointer-events:none}.fly-gantt__handle{fill:transparent;cursor:ew-resize}.fly-gantt__connector{fill:var(--_surface);stroke:var(--_accent);stroke-width:1.5;cursor:crosshair;opacity:0;transition:opacity var(--_transition)}.fly-gantt__row:hover .fly-gantt__connector{opacity:1}.fly-gantt__milestone{fill:var(--_accent);stroke:var(--_surface);stroke-width:1}.fly-gantt__milestone--editable{cursor:grab}.fly-gantt__group{fill:var(--_text-subtle);stroke:var(--_text-subtle)}.fly-gantt__link{fill:none;stroke:var(--_link);stroke-width:1.5;pointer-events:none}.fly-gantt__link-hit{fill:none;stroke:transparent;stroke-width:11;pointer-events:stroke;cursor:pointer}.fly-gantt__link-group:hover .fly-gantt__link{stroke:var(--_text)}.fly-gantt__link-group--selected .fly-gantt__link{stroke:var(--_accent);stroke-width:2.5}.fly-gantt__link-delete-bg{fill:var(--_accent)}.fly-gantt__link-delete{cursor:pointer}.fly-gantt__link-delete-x{stroke:var(--_surface);stroke-width:1.75;stroke-linecap:round;fill:none;pointer-events:none}.fly-gantt__link--ghost{stroke-dasharray:4 3;opacity:.8}.fly-gantt__arrowhead{fill:var(--_link)}.fly-gantt__today{stroke:var(--_today);stroke-width:1.5;stroke-dasharray:3 3;pointer-events:none}.fly-gantt__empty,.fly-gantt__overflow{margin:0;padding:12px;color:var(--_text-faint);font-size:12px;text-align:center}.fly-gantt__tooltip{position:fixed;z-index:20;transform:translate(12px,16px);padding:4px 8px;border-radius:6px;background:var(--_text);color:var(--_surface);font-size:12px;white-space:nowrap;pointer-events:none;direction:ltr;unicode-bidi:isolate}\n"] }]
13577
+ }, template: "<!-- Cross-scrolling canvas: a CSS grid whose header row and label column are sticky, so the\r\n time grid scrolls under a pinned header + label tree. All SVG geometry is pre-mirrored for\r\n RTL in the component (see gantt-scale mapX), so this template never branches on direction. -->\r\n<div\r\n class=\"fly-gantt__scroll\"\r\n role=\"grid\"\r\n tabindex=\"0\"\r\n [attr.aria-label]=\"'gantt.aria.grid' | translate\"\r\n [attr.aria-rowcount]=\"visibleRows().length\"\r\n (keydown)=\"onGridKeydown($event)\"\r\n>\r\n <div\r\n class=\"fly-gantt__canvas\"\r\n [style.grid-template-columns]=\"effectiveLabelWidth() + 'px ' + innerWidth() + 'px'\"\r\n [style.grid-template-rows]=\"HEADER_H + 'px ' + bodyHeight() + 'px'\"\r\n >\r\n <!-- \u2500\u2500 Corner (pinned both axes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 <div class=\"fly-gantt__corner\" [style.height.px]=\"HEADER_H\">\r\n @if (resizableLabels()) {\r\n <!-- The divider lives in the corner because the corner is the one cell pinned on BOTH\r\n axes: the grip stays reachable however far the user has scrolled. The full-height\r\n strip down the label pane below is the same gesture, presentational only. -->\r\n <div\r\n class=\"fly-gantt__splitter\"\r\n role=\"separator\"\r\n tabindex=\"0\"\r\n aria-orientation=\"vertical\"\r\n [attr.aria-label]=\"'gantt.aria.label_resize' | translate\"\r\n [attr.aria-valuenow]=\"effectiveLabelWidth()\"\r\n [attr.aria-valuemin]=\"MIN_LABEL_W\"\r\n [attr.aria-valuemax]=\"MAX_LABEL_W\"\r\n (pointerdown)=\"onLabelResizePointerDown($event)\"\r\n (keydown)=\"onLabelResizeKeydown($event)\"\r\n (dblclick)=\"resetLabelWidth()\"\r\n ></div>\r\n }\r\n </div>\r\n\r\n <!-- \u2500\u2500 Time header (pinned top) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 <svg\r\n class=\"fly-gantt__time-header\"\r\n [attr.width]=\"innerWidth()\"\r\n [attr.height]=\"HEADER_H\"\r\n [attr.viewBox]=\"'0 0 ' + innerWidth() + ' ' + HEADER_H\"\r\n aria-hidden=\"true\"\r\n >\r\n @for (b of weekendBands(); track b.key) {\r\n <rect class=\"fly-gantt__weekend\" [attr.x]=\"b.x\" [attr.y]=\"HEADER_UPPER_H\" [attr.width]=\"b.width\" [attr.height]=\"HEADER_LOWER_H\" />\r\n }\r\n @for (t of upperTicks(); track t.key) {\r\n <line class=\"fly-gantt__tick-line\" [attr.x1]=\"t.x\" [attr.y1]=\"0\" [attr.x2]=\"t.x\" [attr.y2]=\"HEADER_H\" />\r\n <text class=\"fly-gantt__tick-label fly-gantt__tick-label--upper\" [attr.x]=\"t.x + t.width / 2\" [attr.y]=\"HEADER_UPPER_H / 2 + 4\">{{ t.label }}</text>\r\n }\r\n @for (t of lowerTicks(); track t.key) {\r\n <line class=\"fly-gantt__tick-line\" [attr.x1]=\"t.x\" [attr.y1]=\"HEADER_UPPER_H\" [attr.x2]=\"t.x\" [attr.y2]=\"HEADER_H\" />\r\n <text class=\"fly-gantt__tick-label\" [attr.x]=\"t.x + t.width / 2\" [attr.y]=\"HEADER_UPPER_H + HEADER_LOWER_H / 2 + 4\">{{ t.label }}</text>\r\n }\r\n @if (todayX() !== null) {\r\n <line class=\"fly-gantt__today\" [attr.x1]=\"todayX()\" [attr.y1]=\"HEADER_UPPER_H\" [attr.x2]=\"todayX()\" [attr.y2]=\"HEADER_H\" />\r\n }\r\n </svg>\r\n\r\n <!-- \u2500\u2500 Label tree (pinned inline-start) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 <div class=\"fly-gantt__labels\" role=\"rowgroup\">\r\n @for (vm of rowVms(); track vm.flat.row.id) {\r\n <div\r\n class=\"fly-gantt__label-row\"\r\n role=\"row\"\r\n tabindex=\"-1\"\r\n [class.fly-gantt__label-row--selected]=\"selectedId() === vm.flat.row.id\"\r\n [class.fly-gantt__label-row--tinted]=\"vm.tint !== null\"\r\n [style.height.px]=\"rowHeight()\"\r\n [style.--fly-gantt-row-tint]=\"vm.tint\"\r\n [attr.aria-selected]=\"selectedId() === vm.flat.row.id\"\r\n [attr.aria-label]=\"vm.ariaLabel\"\r\n (click)=\"onRowClick(vm.flat.row.id)\"\r\n (dblclick)=\"onRowDblClick(vm.flat.row.id)\"\r\n (keydown.enter)=\"onLabelRowKeydown($event, vm.flat.row.id)\"\r\n >\r\n <span class=\"fly-gantt__label-inner\" [style.padding-inline-start.px]=\"indentFor(vm.flat.depth)\">\r\n @if (vm.flat.hasChildren) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-gantt__chevron\"\r\n [class.fly-gantt__chevron--collapsed]=\"isCollapsed(vm.flat.row.id)\"\r\n [attr.aria-label]=\"(isCollapsed(vm.flat.row.id) ? 'gantt.expand' : 'gantt.collapse') | translate\"\r\n [attr.aria-expanded]=\"!isCollapsed(vm.flat.row.id)\"\r\n (click)=\"toggleCollapse(vm.flat.row.id, $event)\"\r\n >\u25B8</button>\r\n } @else {\r\n <span class=\"fly-gantt__chevron-spacer\"></span>\r\n }\r\n @if (vm.color) {\r\n <!-- Colour chip: the row's own colour at full strength, so a milestone stays\r\n identifiable in the label pane where the band tint is deliberately faint. -->\r\n <span class=\"fly-gantt__label-swatch\" [style.background]=\"vm.color\" aria-hidden=\"true\"></span>\r\n }\r\n <span\r\n class=\"fly-gantt__label-text\"\r\n [class.fly-gantt__label-text--group]=\"vm.shape === 'group'\"\r\n [title]=\"vm.flat.row.label\"\r\n >{{ vm.flat.row.label }}</span>\r\n </span>\r\n </div>\r\n }\r\n @if (resizableLabels()) {\r\n <!-- aria-hidden so this presentational twin of the corner separator is not a second\r\n (and structurally invalid) child of the rowgroup in the accessibility tree. -->\r\n <div\r\n class=\"fly-gantt__splitter fly-gantt__splitter--rail\"\r\n aria-hidden=\"true\"\r\n (pointerdown)=\"onLabelResizePointerDown($event)\"\r\n (dblclick)=\"resetLabelWidth()\"\r\n ></div>\r\n }\r\n </div>\r\n\r\n <!-- \u2500\u2500 Time grid body \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 <svg\r\n #bodySvg\r\n class=\"fly-gantt__body\"\r\n [attr.width]=\"innerWidth()\"\r\n [attr.height]=\"bodyHeight()\"\r\n [attr.viewBox]=\"'0 0 ' + innerWidth() + ' ' + bodyHeight()\"\r\n >\r\n <defs>\r\n <marker\r\n [attr.id]=\"markerId\"\r\n markerWidth=\"8\"\r\n markerHeight=\"8\"\r\n refX=\"6\"\r\n refY=\"4\"\r\n orient=\"auto\"\r\n markerUnits=\"userSpaceOnUse\"\r\n >\r\n <path class=\"fly-gantt__arrowhead\" d=\"M0,0 L7,4 L0,8 Z\" />\r\n </marker>\r\n </defs>\r\n\r\n <!-- Row colour bands sit at the very bottom of the stack, so the weekend shading and the\r\n gridlines below still read through them. -->\r\n @for (vm of tintedRows(); track vm.flat.row.id) {\r\n <rect\r\n class=\"fly-gantt__row-band\"\r\n [attr.x]=\"0\"\r\n [attr.y]=\"vm.y\"\r\n [attr.width]=\"innerWidth()\"\r\n [attr.height]=\"rowHeight()\"\r\n [style.fill]=\"vm.tint\"\r\n />\r\n }\r\n\r\n <!-- Weekend shading + vertical gridlines (behind the bars). -->\r\n @for (b of weekendBands(); track b.key) {\r\n <rect class=\"fly-gantt__weekend\" [attr.x]=\"b.x\" [attr.y]=\"0\" [attr.width]=\"b.width\" [attr.height]=\"bodyHeight()\" />\r\n }\r\n @for (t of lowerTicks(); track t.key) {\r\n <line class=\"fly-gantt__grid-line\" [attr.x1]=\"t.x\" [attr.y1]=\"0\" [attr.x2]=\"t.x\" [attr.y2]=\"bodyHeight()\" />\r\n }\r\n\r\n <!-- Rows: a full-width hit rect (click + link drop target) then the shape. -->\r\n @for (vm of rowVms(); track vm.flat.row.id) {\r\n <g class=\"fly-gantt__row\" [attr.data-row-id]=\"vm.flat.row.id\">\r\n <rect\r\n class=\"fly-gantt__row-hit\"\r\n [class.fly-gantt__row-hit--selected]=\"selectedId() === vm.flat.row.id\"\r\n [attr.x]=\"0\"\r\n [attr.y]=\"vm.y\"\r\n [attr.width]=\"innerWidth()\"\r\n [attr.height]=\"rowHeight()\"\r\n (pointerdown)=\"onRowClick(vm.flat.row.id)\"\r\n (dblclick)=\"onRowDblClick(vm.flat.row.id)\"\r\n />\r\n\r\n @switch (vm.shape) {\r\n @case ('group') {\r\n <path class=\"fly-gantt__group\" [attr.d]=\"groupPath(vm)\" [style.fill]=\"vm.color\" [style.stroke]=\"vm.color\" />\r\n }\r\n @case ('milestone') {\r\n <polygon\r\n class=\"fly-gantt__milestone\"\r\n [class.fly-gantt__milestone--editable]=\"vm.editable\"\r\n [attr.points]=\"diamondPoints(vm)\"\r\n [style.fill]=\"vm.color\"\r\n (pointerdown)=\"onBarPointerDown($event, vm, 'move')\"\r\n />\r\n @if (!readonly() && !vm.flat.row.readonly) {\r\n <!-- A diamond has no width, so one connector serves it; it reads as the finish\r\n end, which is what makes a milestone\u2192task drag the FS everyone expects. -->\r\n <circle\r\n class=\"fly-gantt__connector\"\r\n [attr.cx]=\"milestoneConnectorX(vm)\"\r\n [attr.cy]=\"vm.midY\"\r\n r=\"4\"\r\n [attr.aria-label]=\"'gantt.aria.link_handle' | translate: { label: vm.flat.row.label }\"\r\n (pointerdown)=\"onLinkPointerDown($event, vm, 'finish')\"\r\n />\r\n }\r\n }\r\n @case ('bar') {\r\n <g class=\"fly-gantt__bar-group\">\r\n <rect\r\n class=\"fly-gantt__bar\"\r\n [class.fly-gantt__bar--editable]=\"vm.editable\"\r\n [attr.x]=\"vm.x\"\r\n [attr.y]=\"barTop(vm)\"\r\n [attr.width]=\"vm.width\"\r\n [attr.height]=\"barHeight()\"\r\n rx=\"4\"\r\n [style.fill]=\"vm.color\"\r\n (pointerdown)=\"onBarPointerDown($event, vm, 'move')\"\r\n />\r\n @if (vm.progressWidth > 0) {\r\n <rect\r\n class=\"fly-gantt__progress\"\r\n [attr.x]=\"vm.x\"\r\n [attr.y]=\"barTop(vm)\"\r\n [attr.width]=\"vm.progressWidth\"\r\n [attr.height]=\"barHeight()\"\r\n rx=\"4\"\r\n />\r\n }\r\n @if (vm.editable) {\r\n <!-- Resize handles at both edges. -->\r\n <rect\r\n class=\"fly-gantt__handle\"\r\n [attr.x]=\"vm.x\"\r\n [attr.y]=\"barTop(vm)\"\r\n [attr.width]=\"HANDLE_W\"\r\n [attr.height]=\"barHeight()\"\r\n [attr.aria-label]=\"'gantt.aria.resize_start' | translate\"\r\n (pointerdown)=\"onBarPointerDown($event, vm, 'resize-start')\"\r\n />\r\n <rect\r\n class=\"fly-gantt__handle\"\r\n [attr.x]=\"vm.x + vm.width - HANDLE_W\"\r\n [attr.y]=\"barTop(vm)\"\r\n [attr.width]=\"HANDLE_W\"\r\n [attr.height]=\"barHeight()\"\r\n [attr.aria-label]=\"'gantt.aria.resize_end' | translate\"\r\n (pointerdown)=\"onBarPointerDown($event, vm, 'resize-end')\"\r\n />\r\n }\r\n @if (!readonly() && !vm.flat.row.readonly) {\r\n <!-- One connector per END. Dragging from the start edge yields an SS/SF link\r\n and from the finish edge an FS/FF one \u2014 the drop edge picks which. -->\r\n <circle\r\n class=\"fly-gantt__connector\"\r\n [attr.cx]=\"vm.startX\"\r\n [attr.cy]=\"vm.midY\"\r\n r=\"4\"\r\n [attr.aria-label]=\"'gantt.aria.link_handle_start' | translate: { label: vm.flat.row.label }\"\r\n (pointerdown)=\"onLinkPointerDown($event, vm, 'start')\"\r\n />\r\n <circle\r\n class=\"fly-gantt__connector\"\r\n [attr.cx]=\"vm.endX\"\r\n [attr.cy]=\"vm.midY\"\r\n r=\"4\"\r\n [attr.aria-label]=\"'gantt.aria.link_handle' | translate: { label: vm.flat.row.label }\"\r\n (pointerdown)=\"onLinkPointerDown($event, vm, 'finish')\"\r\n />\r\n }\r\n </g>\r\n }\r\n }\r\n </g>\r\n }\r\n\r\n <!-- Dependency arrows (skipped for missing / collapsed endpoints). Each carries a fat\r\n transparent twin so a 1.5px line is still clickable at pointer precision. -->\r\n @for (link of linkVms(); track link.key) {\r\n <g class=\"fly-gantt__link-group\" [class.fly-gantt__link-group--selected]=\"selectedLinkKey() === link.key\">\r\n <path\r\n class=\"fly-gantt__link-hit\"\r\n [attr.d]=\"link.path\"\r\n [attr.aria-label]=\"link.ariaLabel\"\r\n (click)=\"onLinkClick($event, link)\"\r\n />\r\n <path class=\"fly-gantt__link\" [attr.d]=\"link.path\" [attr.marker-end]=\"'url(#' + markerId + ')'\" />\r\n @if (selectedLinkKey() === link.key && !readonly()) {\r\n <g\r\n class=\"fly-gantt__link-delete\"\r\n [attr.transform]=\"'translate(' + link.badgeX + ',' + link.badgeY + ')'\"\r\n [attr.aria-label]=\"'gantt.aria.delete_link' | translate\"\r\n (click)=\"deleteLink($event, link)\"\r\n >\r\n <circle class=\"fly-gantt__link-delete-bg\" r=\"8\" />\r\n <path class=\"fly-gantt__link-delete-x\" d=\"M-3.5,-3.5 L3.5,3.5 M3.5,-3.5 L-3.5,3.5\" />\r\n </g>\r\n }\r\n </g>\r\n }\r\n\r\n <!-- In-flight link rubber band. -->\r\n @if (linkGesturePath(); as gp) {\r\n <path class=\"fly-gantt__link fly-gantt__link--ghost\" [attr.d]=\"gp\" [attr.marker-end]=\"'url(#' + markerId + ')'\" />\r\n }\r\n\r\n <!-- Today line (over the bars). -->\r\n @if (todayX() !== null) {\r\n <line class=\"fly-gantt__today\" [attr.x1]=\"todayX()\" [attr.y1]=\"0\" [attr.x2]=\"todayX()\" [attr.y2]=\"bodyHeight()\" />\r\n }\r\n </svg>\r\n </div>\r\n\r\n @if (visibleRows().length === 0) {\r\n <p class=\"fly-gantt__empty\">{{ 'gantt.no_data' | translate }}</p>\r\n }\r\n @if (overflowCount() > 0) {\r\n <p class=\"fly-gantt__overflow\">\r\n {{ 'gantt.overflow' | translate: { shown: maxRows(), total: maxRows() + overflowCount() } }}\r\n </p>\r\n }\r\n</div>\r\n\r\n<!-- Drag candidate tooltip (HTML overlay, follows the cursor). -->\r\n@if (dragTooltip(); as tip) {\r\n <div class=\"fly-gantt__tooltip\" [style.left.px]=\"tip.x\" [style.top.px]=\"tip.y\">{{ tip.text }}</div>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--bg-2, var(--surface-card, #fff));--_surface-alt: var(--bg-3, var(--glass-bg-elevated, #f8fafc));--_surface-hover: var(--bg-hover, var(--surface-hover, #f1f5f9));--_border: var(--w08, var(--surface-border, #e2e8f0));--_border-strong: var(--w1, var(--surface-border, #cbd5e1));--_grid-line: var(--line-3, var(--surface-border, #eef2f7));--_text: var(--ink, var(--text-color, #0f172a));--_text-subtle: var(--ink-3, var(--text-color-secondary, #64748b));--_text-faint: var(--ink-4, var(--text-color-secondary, #94a3b8));--_accent: var(--accent);--_bar: var(--_accent);--_bar-progress: color-mix(in srgb, var(--_accent) 72%, #000);--_weekend: color-mix(in srgb, var(--_text-subtle) 9%, transparent);--_today: var(--danger, #ef4444);--_link: var(--_text-subtle);--_radius: var(--r-lg, 8px);--_transition: var(--t-state, .12s ease);display:block;inline-size:100%;block-size:100%;min-block-size:200px;color:var(--_text);font-size:13px}:host(.fly-gantt--resizing){cursor:col-resize;-webkit-user-select:none;user-select:none}.fly-gantt__scroll{position:relative;inline-size:100%;block-size:100%;overflow:auto;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);box-shadow:var(--shadow-card, none);outline:none}.fly-gantt__scroll:focus-visible{outline:2px solid var(--_accent);outline-offset:-2px}.fly-gantt__canvas{display:grid;position:relative}.fly-gantt__corner{position:sticky;inset-block-start:0;inset-inline-start:0;z-index:4;background:var(--_surface-alt);-webkit-backdrop-filter:blur(12px) saturate(140%);backdrop-filter:blur(12px) saturate(140%);border-inline-end:1px solid var(--_border);border-block-end:1px solid var(--_border-strong)}.fly-gantt__time-header{position:sticky;inset-block-start:0;z-index:3;display:block;background:var(--_surface-alt);-webkit-backdrop-filter:blur(12px) saturate(140%);backdrop-filter:blur(12px) saturate(140%);border-block-end:1px solid var(--_border-strong)}.fly-gantt__tick-line{stroke:var(--_grid-line);stroke-width:1}.fly-gantt__tick-label{fill:var(--_text-subtle);font-size:11px;text-anchor:middle;dominant-baseline:middle}.fly-gantt__tick-label--upper{fill:var(--_text);font-weight:600}.fly-gantt__splitter{position:absolute;inset-block:0;inset-inline-end:-3px;inline-size:7px;z-index:5;cursor:col-resize;touch-action:none}.fly-gantt__splitter:after{content:\"\";position:absolute;inset-block:0;inset-inline-start:3px;inline-size:1px;background:transparent;transition:background var(--_transition)}.fly-gantt__splitter:hover:after,.fly-gantt__splitter:focus-visible:after{background:var(--_accent)}.fly-gantt__splitter:focus-visible{outline:2px solid var(--_accent);outline-offset:-1px}:host(.fly-gantt--resizing) .fly-gantt__splitter:after{background:var(--_accent)}.fly-gantt__labels{position:sticky;inset-inline-start:0;z-index:2;background:var(--_surface);border-inline-end:1px solid var(--_border)}.fly-gantt__label-row{position:relative;display:flex;align-items:center;border-block-end:1px solid var(--_grid-line);cursor:pointer;transition:background var(--_transition)}.fly-gantt__label-row:hover{background:var(--_surface-hover)}.fly-gantt__label-row--selected{background:var(--_surface-hover);box-shadow:inset 3px 0 0 0 var(--_accent)}.fly-gantt__label-row--tinted:before{content:\"\";position:absolute;inset:0;background:var(--fly-gantt-row-tint);pointer-events:none}.fly-gantt__label-inner{position:relative;display:flex;align-items:center;gap:4px;inline-size:100%;min-inline-size:0;padding-inline-end:8px}.fly-gantt__label-swatch{flex:0 0 auto;inline-size:8px;block-size:8px;border-radius:2px}.fly-gantt__chevron{flex:0 0 auto;inline-size:18px;block-size:18px;padding:0;border:none;background:transparent;color:var(--_text-subtle);cursor:pointer;line-height:1;transform:rotate(90deg);transition:transform var(--_transition)}.fly-gantt__chevron--collapsed{transform:rotate(0)}:host(.fly-gantt--rtl) .fly-gantt__chevron--collapsed{transform:rotate(180deg)}.fly-gantt__chevron:focus-visible{outline:2px solid var(--_accent);outline-offset:1px;border-radius:4px}.fly-gantt__chevron-spacer{flex:0 0 auto;inline-size:18px}.fly-gantt__label-text{min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-gantt__label-text--group{font-weight:600}.fly-gantt__body{display:block;touch-action:pan-x pan-y}.fly-gantt__row-band{pointer-events:none}.fly-gantt__weekend{fill:var(--_weekend)}.fly-gantt__grid-line{stroke:var(--_grid-line);stroke-width:1}.fly-gantt__row-hit{fill:transparent}.fly-gantt__row-hit--selected{fill:var(--_surface-hover)}.fly-gantt__bar{fill:var(--_bar);stroke:none}.fly-gantt__bar--editable{cursor:grab}.fly-gantt__progress{fill:var(--_bar-progress);pointer-events:none}.fly-gantt__handle{fill:transparent;cursor:ew-resize}.fly-gantt__connector{fill:var(--_surface);stroke:var(--_accent);stroke-width:1.5;cursor:crosshair;opacity:0;transition:opacity var(--_transition)}.fly-gantt__row:hover .fly-gantt__connector{opacity:1}.fly-gantt__milestone{fill:var(--_accent);stroke:var(--_surface);stroke-width:1}.fly-gantt__milestone--editable{cursor:grab}.fly-gantt__group{fill:var(--_text-subtle);stroke:var(--_text-subtle)}.fly-gantt__link{fill:none;stroke:var(--_link);stroke-width:1.5;pointer-events:none}.fly-gantt__link-hit{fill:none;stroke:transparent;stroke-width:11;pointer-events:stroke;cursor:pointer}.fly-gantt__link-group:hover .fly-gantt__link{stroke:var(--_text)}.fly-gantt__link-group--selected .fly-gantt__link{stroke:var(--_accent);stroke-width:2.5}.fly-gantt__link-delete-bg{fill:var(--_accent)}.fly-gantt__link-delete{cursor:pointer}.fly-gantt__link-delete-x{stroke:var(--_surface);stroke-width:1.75;stroke-linecap:round;fill:none;pointer-events:none}.fly-gantt__link--ghost{stroke-dasharray:4 3;opacity:.8}.fly-gantt__arrowhead{fill:var(--_link)}.fly-gantt__today{stroke:var(--_today);stroke-width:1.5;stroke-dasharray:3 3;pointer-events:none}.fly-gantt__empty,.fly-gantt__overflow{margin:0;padding:12px;color:var(--_text-faint);font-size:12px;text-align:center}.fly-gantt__tooltip{position:fixed;z-index:20;transform:translate(12px,16px);padding:4px 8px;border-radius:6px;background:var(--_text);color:var(--_surface);font-size:12px;white-space:nowrap;pointer-events:none;direction:ltr;unicode-bidi:isolate}\n"] }]
13556
13578
  }], propDecorators: { rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], dependencies: [{ type: i0.Input, args: [{ isSignal: true, alias: "dependencies", required: false }] }], zoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoom", required: false }] }], showToday: [{ type: i0.Input, args: [{ isSignal: true, alias: "showToday", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], labelWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelWidth", required: false }] }], resizableLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizableLabels", required: false }] }], rowHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowHeight", required: false }] }], maxRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxRows", required: false }] }], rowDatesChange: [{ type: i0.Output, args: ["rowDatesChange"] }], dependencyCreate: [{ type: i0.Output, args: ["dependencyCreate"] }], dependencyDelete: [{ type: i0.Output, args: ["dependencyDelete"] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], rowDblClick: [{ type: i0.Output, args: ["rowDblClick"] }], labelWidthChange: [{ type: i0.Output, args: ["labelWidthChange"] }], bodyRef: [{ type: i0.ViewChild, args: ['bodySvg', { isSignal: true }] }] } });
13557
13579
 
13558
13580
  // ── Bundled ISO 3166-1 country list ──────────────────────────────────────────
@@ -16253,7 +16275,7 @@ function projectSearch(search, handlers) {
16253
16275
  : { filtersActive: search.filtersActive }),
16254
16276
  hasFilters: !!search.onOpenFilters,
16255
16277
  query: (text) => handlers.search()?.onQuery?.(text),
16256
- openFilters: () => handlers.search()?.onOpenFilters?.(),
16278
+ openFilters: (trigger) => handlers.search()?.onOpenFilters?.(trigger),
16257
16279
  };
16258
16280
  }
16259
16281
  function projectAction(action, handlers) {
@@ -16261,7 +16283,7 @@ function projectAction(action, handlers) {
16261
16283
  return {
16262
16284
  ...stripHandlers(action),
16263
16285
  menu: action.menu ? projectMenu(action, handlers) : null,
16264
- run: () => handlers.action(id)?.onSelect?.(),
16286
+ run: (trigger) => handlers.action(id)?.onSelect?.(trigger),
16265
16287
  };
16266
16288
  }
16267
16289
  function projectMenu(action, handlers) {
@@ -17255,7 +17277,11 @@ class FlyMagicActionsComponent {
17255
17277
  this.toggleMenu(action.id, event.currentTarget);
17256
17278
  return;
17257
17279
  }
17258
- action.run();
17280
+ // The button the user pressed, handed to the publisher so an action that opens
17281
+ // an app-owned popover can anchor it to its own trigger — see
17282
+ // `MagicBarActionView.run`. `currentTarget` (not `target`) is the `.fma-btn`
17283
+ // itself rather than whichever `<svg>` node inside it took the pointer.
17284
+ action.run(event.currentTarget);
17259
17285
  }
17260
17286
  onMenuAction(action, value) {
17261
17287
  // Late-bound: `action` is this render's view, but `select()` always resolves
@@ -19911,7 +19937,7 @@ class FlyCurrencySelectorComponent {
19911
19937
  useExisting: forwardRef(() => FlyCurrencySelectorComponent),
19912
19938
  multi: true,
19913
19939
  },
19914
- ], 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 });
19940
+ ], 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", "flyClickOutsideIgnore"], outputs: ["flyClickOutside"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
19915
19941
  }
19916
19942
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyCurrencySelectorComponent, decorators: [{
19917
19943
  type: Component,
@@ -22104,6 +22130,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
22104
22130
  }]
22105
22131
  }] });
22106
22132
 
22133
+ /** Gap between the trigger and a floating panel, and the breathing room kept at a viewport edge. */
22134
+ const ANCHOR_GAP = 10;
22135
+ const EDGE_PAD = 16;
22136
+ /**
22137
+ * How far the panel's trailing edge overhangs the trigger's, matching the canon
22138
+ * dialog's `right: -4px`. The panel is many times wider than the button, so
22139
+ * aligning the two edges exactly reads as a near-miss; the deliberate overhang
22140
+ * reads as intentional.
22141
+ */
22142
+ const ANCHOR_OVERHANG = 4;
22107
22143
  /**
22108
22144
  * Collapsible advanced-filter panel — structural port of the reference
22109
22145
  * `.signals-page-fp` block (head row + optional status section + field area +
@@ -22128,6 +22164,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
22128
22164
  * - Deferred pages set `showSearchButton` true, drop the per-field handlers,
22129
22165
  * and commit staged criteria on `(search)`.
22130
22166
  *
22167
+ * ## Three placements, and how to pick one
22168
+ * - **In flow** (neither `anchored` nor `anchorEl`) — a block in the page. Opening
22169
+ * it pushes the content below it down. Only right for a page whose filters are
22170
+ * part of its permanent furniture.
22171
+ * - **`[anchored]`** — a dropdown pinned to the trailing edge of a `position:
22172
+ * relative` ancestor the page supplies (the wrapper around its search box).
22173
+ * - **`[anchorEl]`** — a dropdown pinned to a specific ELEMENT anywhere on screen,
22174
+ * including one outside this component's own DOM subtree. See {@link anchorEl}.
22175
+ *
22176
+ * `anchorEl` wins when both are set, so a page can bind it optionally and fall
22177
+ * back to `anchored` in whichever mode has no element to offer.
22178
+ *
22131
22179
  * Matching the source, the panel renders nothing while `open` is false.
22132
22180
  */
22133
22181
  class FlyFilterPanelComponent {
@@ -22146,18 +22194,66 @@ class FlyFilterPanelComponent {
22146
22194
  * `[expandable]`.
22147
22195
  */
22148
22196
  anchored = input(false, ...(ngDevMode ? [{ debugName: "anchored" }] : /* istanbul ignore next */ []));
22197
+ /**
22198
+ * The element this panel hangs off — a dropdown pinned under a specific trigger
22199
+ * rather than under whichever ancestor happens to be positioned.
22200
+ *
22201
+ * ## The case it exists for
22202
+ * A listing screen's filter toggle does not necessarily live in the screen.
22203
+ * Embedded in the desktop shell, the toggle is a magic-bar action painted by
22204
+ * `fly-magic-actions` in the top-bar pill — the shell's DOM, above the window,
22205
+ * across a federation boundary. `[anchored]` cannot reach it: it pins to a
22206
+ * positioned ancestor, and the nearest one is inside the app's own page header.
22207
+ * PPM's projects register shipped exactly that way, and the result was a panel
22208
+ * floating mid-window while the button that opened it sat in the chrome above,
22209
+ * with nothing tying the two together. `MagicBarActionView.run(trigger)` hands
22210
+ * the publisher that button; binding it here is the other half.
22211
+ *
22212
+ * ## What changes when it is set
22213
+ * The host is **portalled to `<body>`** and switched to `position: fixed`. Both
22214
+ * are forced rather than chosen: a shell window establishes a containing block
22215
+ * (`transform` / `backdrop-filter`) and clips its content box, so a panel left in
22216
+ * the app's subtree resolves `fixed` against the window instead of the viewport
22217
+ * AND is clipped at the window's top edge — which is exactly where a panel
22218
+ * hanging off the chrome above it needs to paint. Placement is then measured from
22219
+ * the anchor's rect on every open, resize and scroll, mirrored under RTL, and
22220
+ * clamped so a trigger near a viewport edge still opens fully on screen.
22221
+ *
22222
+ * ## Lifetime
22223
+ * `null` (the default) restores the in-flow/`anchored` behaviour, and the host
22224
+ * returns to where it was declared. Bind it to a signal your open-state clears —
22225
+ * the element the magic bar hands you is the button AS PAINTED, and a re-publish
22226
+ * or a mode switch destroys it.
22227
+ */
22228
+ anchorEl = input(null, ...(ngDevMode ? [{ debugName: "anchorEl" }] : /* istanbul ignore next */ []));
22149
22229
  /** Clear-filters pressed — the page resets its own filter state. */
22150
22230
  cleared = output();
22151
- /** Close (×) pressed — the page flips its open flag. */
22231
+ /** Close (×) pressed, or a dismissing press outside — the page flips its open flag. */
22152
22232
  closed = output();
22153
22233
  /** Search pressed — deferred pages commit staged criteria and query. */
22154
22234
  searched = output();
22155
22235
  /** Whether the `[filter-more]` advanced fields are revealed. */
22156
22236
  showMore = signal(false, ...(ngDevMode ? [{ debugName: "showMore" }] : /* istanbul ignore next */ []));
22237
+ /** True while the panel is a `<body>`-portalled, anchor-positioned dropdown. */
22238
+ floating = computed(() => this.open() && this.anchorEl() !== null, ...(ngDevMode ? [{ debugName: "floating" }] : /* istanbul ignore next */ []));
22239
+ /**
22240
+ * Whether a press outside the panel should dismiss it.
22241
+ *
22242
+ * Only while the panel FLOATS over the page. In flow it is a block OF the page,
22243
+ * and closing it because the user clicked the table it filters would discard
22244
+ * staged criteria on an unrelated interaction. Floating, the opposite holds: an
22245
+ * overlay that survives a click elsewhere is the "panel will not collapse on
22246
+ * click-away" bug.
22247
+ */
22248
+ dismissable = computed(() => this.open() && (this.anchored() || this.floating()), ...(ngDevMode ? [{ debugName: "dismissable" }] : /* istanbul ignore next */ []));
22157
22249
  _host = inject(ElementRef);
22250
+ _doc = inject(DOCUMENT$1);
22158
22251
  _destroyRef = inject(DestroyRef);
22159
22252
  _clipObserver = null;
22160
22253
  _placeRaf = 0;
22254
+ /** Marks where the host was declared, so un-portalling puts it back exactly. */
22255
+ _portalHome = null;
22256
+ _floatTeardown = null;
22161
22257
  constructor() {
22162
22258
  // Anchored placement: the CSS pins the panel's inline-end to the anchor
22163
22259
  // wrapper, but the wrapper is usually a small search box that can sit
@@ -22168,10 +22264,22 @@ class FlyFilterPanelComponent {
22168
22264
  // with a physical translate + width clamp so the panel always stays inside,
22169
22265
  // whichever side of the window the anchor is on. Physical-pixel deltas are
22170
22266
  // direction-agnostic, so the same clamp holds under RTL.
22267
+ //
22268
+ // Floating placement is the same idea against a different box — the viewport,
22269
+ // and an anchor that need not be anywhere near this component's subtree.
22171
22270
  afterRenderEffect(() => {
22172
- const active = this.anchored() && this.open();
22271
+ const floating = this.floating();
22272
+ const anchored = this.anchored() && this.open();
22173
22273
  untracked(() => {
22174
- if (!active) {
22274
+ if (floating) {
22275
+ this._teardownPlacement();
22276
+ this._portal();
22277
+ this._placeFloating();
22278
+ this._observeFloating();
22279
+ return;
22280
+ }
22281
+ this._teardownFloating();
22282
+ if (!anchored) {
22175
22283
  this._teardownPlacement();
22176
22284
  return;
22177
22285
  }
@@ -22179,8 +22287,124 @@ class FlyFilterPanelComponent {
22179
22287
  this._observeClipAncestor();
22180
22288
  });
22181
22289
  });
22182
- this._destroyRef.onDestroy(() => this._teardownPlacement());
22290
+ this._destroyRef.onDestroy(() => {
22291
+ this._teardownPlacement();
22292
+ this._teardownFloating();
22293
+ });
22294
+ }
22295
+ // ── Floating (anchorEl) placement ───────────────────────────────────────────
22296
+ /**
22297
+ * Move the host to `<body>`, leaving a comment node where it was declared.
22298
+ *
22299
+ * The comment is what makes this reversible. Angular keeps rendering into the
22300
+ * host wherever it sits — relocating a DOM node does not detach a view — but the
22301
+ * component cannot ask the DOM "where were you?" after the fact, and the original
22302
+ * parent may itself be torn down while the panel is open. Un-portalling therefore
22303
+ * re-inserts before the marker while the marker is still attached, and simply
22304
+ * leaves the host where it is otherwise.
22305
+ */
22306
+ _portal() {
22307
+ const host = this._host.nativeElement;
22308
+ const body = this._doc.body;
22309
+ if (!body || host.parentNode === body)
22310
+ return;
22311
+ if (!this._portalHome) {
22312
+ this._portalHome = this._doc.createComment('fly-filter-panel');
22313
+ host.parentNode?.insertBefore(this._portalHome, host);
22314
+ }
22315
+ body.appendChild(host);
22316
+ }
22317
+ _unportal() {
22318
+ const host = this._host.nativeElement;
22319
+ const home = this._portalHome;
22320
+ this._portalHome = null;
22321
+ if (home?.parentNode) {
22322
+ home.parentNode.insertBefore(host, home);
22323
+ home.parentNode.removeChild(home);
22324
+ return;
22325
+ }
22326
+ // Nowhere to go home to: the declaring parent was torn down while the panel was
22327
+ // open (a route change, a closing window). Angular has already destroyed the view,
22328
+ // but the host is OUR node in `<body>` and nothing else will collect it — leaving it
22329
+ // there strands an empty panel element in the document for the rest of the session.
22330
+ if (host.parentNode === this._doc.body)
22331
+ this._doc.body.removeChild(host);
22332
+ }
22333
+ /**
22334
+ * Pin the panel under {@link anchorEl}: {@link ANCHOR_GAP} below its bottom edge,
22335
+ * its trailing edge overhanging the anchor's by {@link ANCHOR_OVERHANG}, clamped
22336
+ * into the viewport on both axes.
22337
+ *
22338
+ * Trailing edge, not "right": the anchor's OWN computed direction decides which
22339
+ * physical side that is, so the panel opens inward from the trigger under both LTR
22340
+ * and RTL with nothing passed by the caller. The final clamp is in physical pixels
22341
+ * and therefore direction-agnostic — a trigger 30px from the viewport edge yields
22342
+ * the same on-screen result either way.
22343
+ */
22344
+ _placeFloating() {
22345
+ const host = this._host.nativeElement;
22346
+ const anchor = this.anchorEl();
22347
+ const win = host.ownerDocument.defaultView;
22348
+ if (!anchor || !win)
22349
+ return;
22350
+ const rect = anchor.getBoundingClientRect();
22351
+ const vw = win.innerWidth;
22352
+ const vh = win.innerHeight;
22353
+ // Clamp the width FIRST: the host's own rect below has to reflect it, and a
22354
+ // panel wider than the viewport makes every horizontal clamp meaningless.
22355
+ host.style.setProperty('--fp-max-w', `${Math.max(240, Math.round(vw - 2 * EDGE_PAD))}px`);
22356
+ const top = Math.min(rect.bottom + ANCHOR_GAP, Math.max(EDGE_PAD, vh - EDGE_PAD));
22357
+ host.style.setProperty('--fp-y', `${Math.round(top)}px`);
22358
+ host.style.setProperty('--fp-max-h', `${Math.max(160, Math.round(vh - top - EDGE_PAD))}px`);
22359
+ // Read AFTER the width clamp and the block start are applied, so the measured
22360
+ // box is the final one.
22361
+ const width = host.getBoundingClientRect().width;
22362
+ const rtl = win.getComputedStyle(anchor).direction === 'rtl';
22363
+ const raw = rtl ? rect.left - ANCHOR_OVERHANG : rect.right + ANCHOR_OVERHANG - width;
22364
+ const maxLeft = Math.max(EDGE_PAD, vw - EDGE_PAD - width);
22365
+ host.style.setProperty('--fp-x', `${Math.round(Math.min(Math.max(raw, EDGE_PAD), maxLeft))}px`);
22366
+ }
22367
+ /**
22368
+ * Re-place on anything that can move the anchor under an already-open panel: the
22369
+ * viewport resizing, the shell window being dragged or resized (which moves or
22370
+ * resizes the anchor's own box), and scrolling ANYWHERE — captured, because the
22371
+ * anchor lives in the chrome and the scroller that shifts it is not necessarily an
22372
+ * ancestor of this host, which is now in `<body>`.
22373
+ */
22374
+ _observeFloating() {
22375
+ if (this._floatTeardown)
22376
+ return;
22377
+ const host = this._host.nativeElement;
22378
+ const win = host.ownerDocument.defaultView;
22379
+ const anchor = this.anchorEl();
22380
+ if (!win)
22381
+ return;
22382
+ const replace = () => {
22383
+ cancelAnimationFrame(this._placeRaf);
22384
+ this._placeRaf = requestAnimationFrame(() => this._placeFloating());
22385
+ };
22386
+ win.addEventListener('resize', replace);
22387
+ win.addEventListener('scroll', replace, true);
22388
+ const observer = anchor && typeof ResizeObserver !== 'undefined' ? new ResizeObserver(replace) : null;
22389
+ if (anchor && observer)
22390
+ observer.observe(anchor);
22391
+ this._floatTeardown = () => {
22392
+ win.removeEventListener('resize', replace);
22393
+ win.removeEventListener('scroll', replace, true);
22394
+ observer?.disconnect();
22395
+ cancelAnimationFrame(this._placeRaf);
22396
+ };
22183
22397
  }
22398
+ _teardownFloating() {
22399
+ this._floatTeardown?.();
22400
+ this._floatTeardown = null;
22401
+ this._unportal();
22402
+ const host = this._host.nativeElement;
22403
+ for (const prop of ['--fp-x', '--fp-y', '--fp-max-h', '--fp-max-w']) {
22404
+ host.style.removeProperty(prop);
22405
+ }
22406
+ }
22407
+ // ── Anchored (positioned-ancestor) placement ────────────────────────────────
22184
22408
  _place() {
22185
22409
  const host = this._host.nativeElement;
22186
22410
  const win = host.ownerDocument.defaultView;
@@ -22191,14 +22415,13 @@ class FlyFilterPanelComponent {
22191
22415
  host.style.removeProperty('--fp-max-w');
22192
22416
  const clipRect = this._clipAncestor(host)?.getBoundingClientRect();
22193
22417
  const bounds = clipRect ?? new DOMRect(0, 0, win.innerWidth, win.innerHeight);
22194
- const pad = 16;
22195
- const availableWidth = Math.max(240, bounds.width - 2 * pad);
22418
+ const availableWidth = Math.max(240, bounds.width - 2 * EDGE_PAD);
22196
22419
  host.style.setProperty('--fp-max-w', `${Math.round(availableWidth)}px`);
22197
22420
  // Setting the width clamp above forces layout before this read, so the rect
22198
22421
  // already reflects the clamped size.
22199
22422
  const rect = host.getBoundingClientRect();
22200
- const minLeft = bounds.left + pad;
22201
- const maxRight = bounds.right - pad;
22423
+ const minLeft = bounds.left + EDGE_PAD;
22424
+ const maxRight = bounds.right - EDGE_PAD;
22202
22425
  let shift = 0;
22203
22426
  if (rect.left < minLeft)
22204
22427
  shift = minLeft - rect.left;
@@ -22241,12 +22464,15 @@ class FlyFilterPanelComponent {
22241
22464
  host.style.removeProperty('--fp-max-w');
22242
22465
  }
22243
22466
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyFilterPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
22244
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyFilterPanelComponent, isStandalone: true, selector: "fly-filter-panel", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: true, transformFunction: null }, titleKey: { classPropertyName: "titleKey", publicName: "titleKey", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, hasMore: { classPropertyName: "hasMore", publicName: "hasMore", isSignal: true, isRequired: false, transformFunction: null }, showSearchButton: { classPropertyName: "showSearchButton", publicName: "showSearchButton", isSignal: true, isRequired: false, transformFunction: null }, anchored: { classPropertyName: "anchored", publicName: "anchored", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cleared: "cleared", closed: "closed", searched: "searched" }, host: { properties: { "class.fp--anchored": "anchored()" } }, ngImport: i0, template: " @if (open()) {\r\n <div class=\"fp\" [attr.data-layout]=\"layout()\">\r\n <div class=\"fp__head\">\r\n <div class=\"fp__title\">{{ titleKey() | translate }}</div>\r\n <div class=\"fp__actions\">\r\n <button type=\"button\" class=\"fp__clear\" (click)=\"cleared.emit()\">\r\n {{ 'ui.action.clearFilters' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fp__close\"\r\n (click)=\"closed.emit()\"\r\n [attr.aria-label]=\"'ui.action.close' | translate\"\r\n >\u00D7</button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"fp__body\">\r\n <ng-content select=\"[filter-status]\" />\r\n\r\n <div class=\"fp__grid\">\r\n <ng-content />\r\n </div>\r\n\r\n @if (hasMore()) {\r\n <div class=\"fp__more\" [hidden]=\"!showMore()\">\r\n <div class=\"fp__grid\">\r\n <ng-content select=\"[filter-more]\" />\r\n </div>\r\n </div>\r\n <div class=\"fp__morebar\">\r\n <button\r\n type=\"button\"\r\n class=\"fp__morebtn\"\r\n (click)=\"showMore.set(!showMore())\"\r\n [attr.aria-expanded]=\"showMore()\"\r\n >\r\n <svg class=\"fp__moreico\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <path [attr.d]=\"showMore() ? 'M3.5 8h9' : 'M8 3.5v9M3.5 8h9'\" />\r\n </svg>\r\n {{ (showMore() ? 'ui.action.fewerOptions' : 'ui.action.addMoreOptions') | translate }}\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (showSearchButton()) {\r\n <div class=\"fp__footer\">\r\n <button type=\"button\" fly-button variant=\"primary\" (click)=\"searched.emit()\">\r\n {{ 'ui.action.search' | translate }}\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n }\r\n \r\n", styles: ["@charset \"UTF-8\";:host{display:block;container-type:inline-size}.fp{margin-block-end:var(--sp-4);border-radius:var(--r-lg);overflow:hidden;animation:fly-fp-in var(--t-overlay);border:1px solid var(--glass2-border);background-image:var(--glass2-bg);box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset)}@media(prefers-reduced-transparency:reduce){.fp{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.fp:before,.fp:after{display:none}}@media(prefers-contrast:more){.fp{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.fp:after{animation:none}}:host(.fp--anchored){--fp-shift-x: 0px;--fp-max-w: calc(100vw - 2 * var(--sp-4));position:absolute;inset-block-start:calc(100% + var(--sp-2));inset-inline-end:0;z-index:var(--z-overlay);width:min(600px,var(--fp-max-w));transform:translate(var(--fp-shift-x))}:host(.fp--anchored) .fp{margin-block-end:0}:host(.fp--anchored) .fp__body{max-height:min(320px,44vh);overflow-y:auto}.fp__head{display:flex;align-items:center;justify-content:space-between;padding:var(--sp-3) var(--sp-4);border-block-end:.5px solid var(--w1)}.fp__title{font-size:var(--text-xs);font-weight:var(--fw-medium);letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3)}.fp__actions{display:flex;align-items:center;gap:var(--sp-1)}.fp__clear{border:0;background:transparent;font-size:12px;color:var(--ink-3);padding:var(--sp-1) var(--sp-2);border-radius:var(--r-sm);cursor:pointer;font-family:inherit;transition:background var(--t-state),color var(--t-state)}.fp__clear:hover{color:var(--ink);background:var(--w08)}.fp__clear:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fp__close{width:24px;height:24px;border:0;background:transparent;color:var(--ink-3);cursor:pointer;border-radius:var(--r-sm);font-size:var(--text-xl);line-height:1;display:grid;place-items:center}.fp__close:hover{color:var(--ink);background:var(--w08)}.fp__close:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fp ::ng-deep [filter-status]{display:flex;flex-direction:column;gap:var(--sp-2);padding:14px var(--sp-4);border-block-end:.5px solid var(--w08)}.fp__grid{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:14px var(--sp-4);padding:var(--sp-4)}@container (width <= 1180px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(4,minmax(0,1fr))}}@media(width<=1180px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(4,minmax(0,1fr))}}@container (width <= 820px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(width<=820px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(2,minmax(0,1fr))}}.fp[data-layout=rows] .fp__grid{grid-template-columns:1fr;gap:var(--sp-2)}.fp[data-layout=rows] ::ng-deep .fld{display:grid;grid-template-columns:minmax(110px,168px) 1fr;align-items:center;gap:var(--sp-1) var(--sp-4)}.fp[data-layout=rows] ::ng-deep .fld__l{margin:0;color:var(--ink-2)}.fp[data-layout=rows] ::ng-deep .fld__hint,.fp[data-layout=rows] ::ng-deep .fld__err{grid-column:2}@container (width <= 640px){.fp[data-layout=rows] ::ng-deep .fld{grid-template-columns:1fr}.fp[data-layout=rows] ::ng-deep .fld__hint,.fp[data-layout=rows] ::ng-deep .fld__err{grid-column:1}}@media(width<=640px){.fp[data-layout=rows] ::ng-deep .fld{grid-template-columns:1fr}.fp[data-layout=rows] ::ng-deep .fld__hint,.fp[data-layout=rows] ::ng-deep .fld__err{grid-column:1}}.fp__more{border-block-start:.5px solid var(--w08)}.fp__more .fp__grid{padding-block-start:var(--sp-3)}.fp__morebar{padding:0 var(--sp-4) var(--sp-3)}.fp__morebtn{display:inline-flex;align-items:center;gap:var(--sp-1);border:0;background:transparent;padding:var(--sp-1) var(--sp-2);border-radius:var(--r-sm);font-family:inherit;font-size:var(--text-sm);font-weight:var(--fw-medium);color:var(--accent);cursor:pointer;transition:background var(--t-state)}.fp__morebtn:hover{background:var(--w08)}.fp__morebtn:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fp__moreico{width:14px;height:14px;flex-shrink:0}.fp__footer{display:flex;justify-content:flex-end;padding:var(--sp-3) var(--sp-4);border-block-start:.5px solid var(--w08)}.fp__footer button{background:var(--grad);border:0;color:var(--ink-inverse);box-shadow:var(--shadow-raised)}.fp__footer button:hover{background:var(--grad);border:0;filter:brightness(1.05);box-shadow:var(--shadow-accent)}@keyframes fly-fp-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}\n"], dependencies: [{ kind: "component", type: FlyButtonComponent, selector: "button[fly-button], a[fly-button]", inputs: ["variant", "size", "loading"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
22467
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyFilterPanelComponent, isStandalone: true, selector: "fly-filter-panel", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: true, transformFunction: null }, titleKey: { classPropertyName: "titleKey", publicName: "titleKey", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, hasMore: { classPropertyName: "hasMore", publicName: "hasMore", isSignal: true, isRequired: false, transformFunction: null }, showSearchButton: { classPropertyName: "showSearchButton", publicName: "showSearchButton", isSignal: true, isRequired: false, transformFunction: null }, anchored: { classPropertyName: "anchored", publicName: "anchored", isSignal: true, isRequired: false, transformFunction: null }, anchorEl: { classPropertyName: "anchorEl", publicName: "anchorEl", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cleared: "cleared", closed: "closed", searched: "searched" }, host: { properties: { "class.fp--anchored": "anchored() && !floating()", "class.fp--floating": "floating()" } }, ngImport: i0, template: " @if (open()) {\r\n <!--\r\n The dismiss host is `.fp`, and the TRIGGER is listed as ignored rather than\r\n wrapped: an anchored panel's toggle can live in another component entirely (the\r\n shell's magic bar), so there is no element that contains both. See the\r\n directive's `flyClickOutsideIgnore` for what goes wrong without it.\r\n -->\r\n <div\r\n class=\"fp\"\r\n [attr.data-layout]=\"layout()\"\r\n [flyClickOutsideEnabled]=\"dismissable()\"\r\n [flyClickOutsideIgnore]=\"anchorEl()\"\r\n (flyClickOutside)=\"closed.emit()\"\r\n >\r\n <div class=\"fp__head\">\r\n <div class=\"fp__title\">{{ titleKey() | translate }}</div>\r\n <div class=\"fp__actions\">\r\n <button type=\"button\" class=\"fp__clear\" (click)=\"cleared.emit()\">\r\n {{ 'ui.action.clearFilters' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fp__close\"\r\n (click)=\"closed.emit()\"\r\n [attr.aria-label]=\"'ui.action.close' | translate\"\r\n >\u00D7</button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"fp__body\">\r\n <ng-content select=\"[filter-status]\" />\r\n\r\n <div class=\"fp__grid\">\r\n <ng-content />\r\n </div>\r\n\r\n @if (hasMore()) {\r\n <div class=\"fp__more\" [hidden]=\"!showMore()\">\r\n <div class=\"fp__grid\">\r\n <ng-content select=\"[filter-more]\" />\r\n </div>\r\n </div>\r\n <div class=\"fp__morebar\">\r\n <button\r\n type=\"button\"\r\n class=\"fp__morebtn\"\r\n (click)=\"showMore.set(!showMore())\"\r\n [attr.aria-expanded]=\"showMore()\"\r\n >\r\n <svg class=\"fp__moreico\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <path [attr.d]=\"showMore() ? 'M3.5 8h9' : 'M8 3.5v9M3.5 8h9'\" />\r\n </svg>\r\n {{ (showMore() ? 'ui.action.fewerOptions' : 'ui.action.addMoreOptions') | translate }}\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (showSearchButton()) {\r\n <div class=\"fp__footer\">\r\n <button type=\"button\" fly-button variant=\"primary\" (click)=\"searched.emit()\">\r\n {{ 'ui.action.search' | translate }}\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n }\r\n \r\n", styles: ["@charset \"UTF-8\";:host{display:block;container-type:inline-size}.fp{margin-block-end:var(--sp-4);border-radius:var(--r-lg);overflow:hidden;animation:fly-fp-in var(--t-overlay);border:1px solid var(--glass2-border);background-image:var(--glass2-bg);box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset)}@media(prefers-reduced-transparency:reduce){.fp{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.fp:before,.fp:after{display:none}}@media(prefers-contrast:more){.fp{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.fp:after{animation:none}}:host(.fp--anchored){--fp-shift-x: 0px;--fp-max-w: calc(100vw - 2 * var(--sp-4));position:absolute;inset-block-start:calc(100% + var(--sp-2));inset-inline-end:0;z-index:var(--z-overlay);width:min(600px,var(--fp-max-w));transform:translate(var(--fp-shift-x))}:host(.fp--floating){--fp-max-w: calc(100vw - 2 * var(--sp-4));--fp-max-h: calc(100vh - 2 * var(--sp-4));--fp-x: 0px;--fp-y: 0px;position:fixed;inset-block-start:var(--fp-y);inset-inline-start:auto;left:var(--fp-x);z-index:var(--z-overlay);width:min(600px,var(--fp-max-w));max-height:var(--fp-max-h)}:host(.fp--floating) .fp{margin-block-end:0;max-height:var(--fp-max-h);display:flex;flex-direction:column}:host(.fp--floating) .fp__body{flex:1 1 auto;min-height:0;overflow-y:auto}:host(.fp--anchored) .fp{margin-block-end:0}:host(.fp--anchored) .fp__body{max-height:min(320px,44vh);overflow-y:auto}.fp__head{display:flex;align-items:center;justify-content:space-between;padding:var(--sp-3) var(--sp-4);border-block-end:.5px solid var(--w1)}.fp__title{font-size:var(--text-xs);font-weight:var(--fw-medium);letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3)}.fp__actions{display:flex;align-items:center;gap:var(--sp-1)}.fp__clear{border:0;background:transparent;font-size:12px;color:var(--ink-3);padding:var(--sp-1) var(--sp-2);border-radius:var(--r-sm);cursor:pointer;font-family:inherit;transition:background var(--t-state),color var(--t-state)}.fp__clear:hover{color:var(--ink);background:var(--w08)}.fp__clear:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fp__close{width:24px;height:24px;border:0;background:transparent;color:var(--ink-3);cursor:pointer;border-radius:var(--r-sm);font-size:var(--text-xl);line-height:1;display:grid;place-items:center}.fp__close:hover{color:var(--ink);background:var(--w08)}.fp__close:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fp ::ng-deep [filter-status]{display:flex;flex-direction:column;gap:var(--sp-2);padding:14px var(--sp-4);border-block-end:.5px solid var(--w08)}.fp__grid{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:14px var(--sp-4);padding:var(--sp-4)}@container (width <= 1180px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(4,minmax(0,1fr))}}@media(width<=1180px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(4,minmax(0,1fr))}}@container (width <= 820px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(width<=820px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(2,minmax(0,1fr))}}.fp[data-layout=rows] .fp__grid{grid-template-columns:1fr;gap:var(--sp-2)}.fp[data-layout=rows] ::ng-deep .fld{display:grid;grid-template-columns:minmax(110px,168px) 1fr;align-items:center;gap:var(--sp-1) var(--sp-4)}.fp[data-layout=rows] ::ng-deep .fld__l{margin:0;color:var(--ink-2)}.fp[data-layout=rows] ::ng-deep .fld__hint,.fp[data-layout=rows] ::ng-deep .fld__err{grid-column:2}@container (width <= 640px){.fp[data-layout=rows] ::ng-deep .fld{grid-template-columns:1fr}.fp[data-layout=rows] ::ng-deep .fld__hint,.fp[data-layout=rows] ::ng-deep .fld__err{grid-column:1}}@media(width<=640px){.fp[data-layout=rows] ::ng-deep .fld{grid-template-columns:1fr}.fp[data-layout=rows] ::ng-deep .fld__hint,.fp[data-layout=rows] ::ng-deep .fld__err{grid-column:1}}.fp__more{border-block-start:.5px solid var(--w08)}.fp__more .fp__grid{padding-block-start:var(--sp-3)}.fp__morebar{padding:0 var(--sp-4) var(--sp-3)}.fp__morebtn{display:inline-flex;align-items:center;gap:var(--sp-1);border:0;background:transparent;padding:var(--sp-1) var(--sp-2);border-radius:var(--r-sm);font-family:inherit;font-size:var(--text-sm);font-weight:var(--fw-medium);color:var(--accent);cursor:pointer;transition:background var(--t-state)}.fp__morebtn:hover{background:var(--w08)}.fp__morebtn:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fp__moreico{width:14px;height:14px;flex-shrink:0}.fp__footer{display:flex;justify-content:flex-end;padding:var(--sp-3) var(--sp-4);border-block-start:.5px solid var(--w08)}.fp__footer button{background:var(--grad);border:0;color:var(--ink-inverse);box-shadow:var(--shadow-raised)}.fp__footer button:hover{background:var(--grad);border:0;filter:brightness(1.05);box-shadow:var(--shadow-accent)}@keyframes fly-fp-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}\n"], dependencies: [{ kind: "component", type: FlyButtonComponent, selector: "button[fly-button], a[fly-button]", inputs: ["variant", "size", "loading"] }, { kind: "directive", type: FlyClickOutsideDirective, selector: "[flyClickOutside]", inputs: ["flyClickOutsideEnabled", "flyClickOutsideIgnore"], outputs: ["flyClickOutside"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
22245
22468
  }
22246
22469
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyFilterPanelComponent, decorators: [{
22247
22470
  type: Component,
22248
- args: [{ selector: 'fly-filter-panel', standalone: true, imports: [TranslatePipe, FlyButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: { '[class.fp--anchored]': 'anchored()' }, template: " @if (open()) {\r\n <div class=\"fp\" [attr.data-layout]=\"layout()\">\r\n <div class=\"fp__head\">\r\n <div class=\"fp__title\">{{ titleKey() | translate }}</div>\r\n <div class=\"fp__actions\">\r\n <button type=\"button\" class=\"fp__clear\" (click)=\"cleared.emit()\">\r\n {{ 'ui.action.clearFilters' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fp__close\"\r\n (click)=\"closed.emit()\"\r\n [attr.aria-label]=\"'ui.action.close' | translate\"\r\n >\u00D7</button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"fp__body\">\r\n <ng-content select=\"[filter-status]\" />\r\n\r\n <div class=\"fp__grid\">\r\n <ng-content />\r\n </div>\r\n\r\n @if (hasMore()) {\r\n <div class=\"fp__more\" [hidden]=\"!showMore()\">\r\n <div class=\"fp__grid\">\r\n <ng-content select=\"[filter-more]\" />\r\n </div>\r\n </div>\r\n <div class=\"fp__morebar\">\r\n <button\r\n type=\"button\"\r\n class=\"fp__morebtn\"\r\n (click)=\"showMore.set(!showMore())\"\r\n [attr.aria-expanded]=\"showMore()\"\r\n >\r\n <svg class=\"fp__moreico\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <path [attr.d]=\"showMore() ? 'M3.5 8h9' : 'M8 3.5v9M3.5 8h9'\" />\r\n </svg>\r\n {{ (showMore() ? 'ui.action.fewerOptions' : 'ui.action.addMoreOptions') | translate }}\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (showSearchButton()) {\r\n <div class=\"fp__footer\">\r\n <button type=\"button\" fly-button variant=\"primary\" (click)=\"searched.emit()\">\r\n {{ 'ui.action.search' | translate }}\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n }\r\n \r\n", styles: ["@charset \"UTF-8\";:host{display:block;container-type:inline-size}.fp{margin-block-end:var(--sp-4);border-radius:var(--r-lg);overflow:hidden;animation:fly-fp-in var(--t-overlay);border:1px solid var(--glass2-border);background-image:var(--glass2-bg);box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset)}@media(prefers-reduced-transparency:reduce){.fp{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.fp:before,.fp:after{display:none}}@media(prefers-contrast:more){.fp{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.fp:after{animation:none}}:host(.fp--anchored){--fp-shift-x: 0px;--fp-max-w: calc(100vw - 2 * var(--sp-4));position:absolute;inset-block-start:calc(100% + var(--sp-2));inset-inline-end:0;z-index:var(--z-overlay);width:min(600px,var(--fp-max-w));transform:translate(var(--fp-shift-x))}:host(.fp--anchored) .fp{margin-block-end:0}:host(.fp--anchored) .fp__body{max-height:min(320px,44vh);overflow-y:auto}.fp__head{display:flex;align-items:center;justify-content:space-between;padding:var(--sp-3) var(--sp-4);border-block-end:.5px solid var(--w1)}.fp__title{font-size:var(--text-xs);font-weight:var(--fw-medium);letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3)}.fp__actions{display:flex;align-items:center;gap:var(--sp-1)}.fp__clear{border:0;background:transparent;font-size:12px;color:var(--ink-3);padding:var(--sp-1) var(--sp-2);border-radius:var(--r-sm);cursor:pointer;font-family:inherit;transition:background var(--t-state),color var(--t-state)}.fp__clear:hover{color:var(--ink);background:var(--w08)}.fp__clear:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fp__close{width:24px;height:24px;border:0;background:transparent;color:var(--ink-3);cursor:pointer;border-radius:var(--r-sm);font-size:var(--text-xl);line-height:1;display:grid;place-items:center}.fp__close:hover{color:var(--ink);background:var(--w08)}.fp__close:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fp ::ng-deep [filter-status]{display:flex;flex-direction:column;gap:var(--sp-2);padding:14px var(--sp-4);border-block-end:.5px solid var(--w08)}.fp__grid{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:14px var(--sp-4);padding:var(--sp-4)}@container (width <= 1180px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(4,minmax(0,1fr))}}@media(width<=1180px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(4,minmax(0,1fr))}}@container (width <= 820px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(width<=820px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(2,minmax(0,1fr))}}.fp[data-layout=rows] .fp__grid{grid-template-columns:1fr;gap:var(--sp-2)}.fp[data-layout=rows] ::ng-deep .fld{display:grid;grid-template-columns:minmax(110px,168px) 1fr;align-items:center;gap:var(--sp-1) var(--sp-4)}.fp[data-layout=rows] ::ng-deep .fld__l{margin:0;color:var(--ink-2)}.fp[data-layout=rows] ::ng-deep .fld__hint,.fp[data-layout=rows] ::ng-deep .fld__err{grid-column:2}@container (width <= 640px){.fp[data-layout=rows] ::ng-deep .fld{grid-template-columns:1fr}.fp[data-layout=rows] ::ng-deep .fld__hint,.fp[data-layout=rows] ::ng-deep .fld__err{grid-column:1}}@media(width<=640px){.fp[data-layout=rows] ::ng-deep .fld{grid-template-columns:1fr}.fp[data-layout=rows] ::ng-deep .fld__hint,.fp[data-layout=rows] ::ng-deep .fld__err{grid-column:1}}.fp__more{border-block-start:.5px solid var(--w08)}.fp__more .fp__grid{padding-block-start:var(--sp-3)}.fp__morebar{padding:0 var(--sp-4) var(--sp-3)}.fp__morebtn{display:inline-flex;align-items:center;gap:var(--sp-1);border:0;background:transparent;padding:var(--sp-1) var(--sp-2);border-radius:var(--r-sm);font-family:inherit;font-size:var(--text-sm);font-weight:var(--fw-medium);color:var(--accent);cursor:pointer;transition:background var(--t-state)}.fp__morebtn:hover{background:var(--w08)}.fp__morebtn:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fp__moreico{width:14px;height:14px;flex-shrink:0}.fp__footer{display:flex;justify-content:flex-end;padding:var(--sp-3) var(--sp-4);border-block-start:.5px solid var(--w08)}.fp__footer button{background:var(--grad);border:0;color:var(--ink-inverse);box-shadow:var(--shadow-raised)}.fp__footer button:hover{background:var(--grad);border:0;filter:brightness(1.05);box-shadow:var(--shadow-accent)}@keyframes fly-fp-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}\n"] }]
22249
- }], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: true }] }], titleKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleKey", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], hasMore: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasMore", required: false }] }], showSearchButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSearchButton", required: false }] }], anchored: [{ type: i0.Input, args: [{ isSignal: true, alias: "anchored", required: false }] }], cleared: [{ type: i0.Output, args: ["cleared"] }], closed: [{ type: i0.Output, args: ["closed"] }], searched: [{ type: i0.Output, args: ["searched"] }] } });
22471
+ args: [{ selector: 'fly-filter-panel', standalone: true, imports: [TranslatePipe, FlyButtonComponent, FlyClickOutsideDirective], changeDetection: ChangeDetectionStrategy.OnPush, host: {
22472
+ '[class.fp--anchored]': 'anchored() && !floating()',
22473
+ '[class.fp--floating]': 'floating()',
22474
+ }, template: " @if (open()) {\r\n <!--\r\n The dismiss host is `.fp`, and the TRIGGER is listed as ignored rather than\r\n wrapped: an anchored panel's toggle can live in another component entirely (the\r\n shell's magic bar), so there is no element that contains both. See the\r\n directive's `flyClickOutsideIgnore` for what goes wrong without it.\r\n -->\r\n <div\r\n class=\"fp\"\r\n [attr.data-layout]=\"layout()\"\r\n [flyClickOutsideEnabled]=\"dismissable()\"\r\n [flyClickOutsideIgnore]=\"anchorEl()\"\r\n (flyClickOutside)=\"closed.emit()\"\r\n >\r\n <div class=\"fp__head\">\r\n <div class=\"fp__title\">{{ titleKey() | translate }}</div>\r\n <div class=\"fp__actions\">\r\n <button type=\"button\" class=\"fp__clear\" (click)=\"cleared.emit()\">\r\n {{ 'ui.action.clearFilters' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fp__close\"\r\n (click)=\"closed.emit()\"\r\n [attr.aria-label]=\"'ui.action.close' | translate\"\r\n >\u00D7</button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"fp__body\">\r\n <ng-content select=\"[filter-status]\" />\r\n\r\n <div class=\"fp__grid\">\r\n <ng-content />\r\n </div>\r\n\r\n @if (hasMore()) {\r\n <div class=\"fp__more\" [hidden]=\"!showMore()\">\r\n <div class=\"fp__grid\">\r\n <ng-content select=\"[filter-more]\" />\r\n </div>\r\n </div>\r\n <div class=\"fp__morebar\">\r\n <button\r\n type=\"button\"\r\n class=\"fp__morebtn\"\r\n (click)=\"showMore.set(!showMore())\"\r\n [attr.aria-expanded]=\"showMore()\"\r\n >\r\n <svg class=\"fp__moreico\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <path [attr.d]=\"showMore() ? 'M3.5 8h9' : 'M8 3.5v9M3.5 8h9'\" />\r\n </svg>\r\n {{ (showMore() ? 'ui.action.fewerOptions' : 'ui.action.addMoreOptions') | translate }}\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (showSearchButton()) {\r\n <div class=\"fp__footer\">\r\n <button type=\"button\" fly-button variant=\"primary\" (click)=\"searched.emit()\">\r\n {{ 'ui.action.search' | translate }}\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n }\r\n \r\n", styles: ["@charset \"UTF-8\";:host{display:block;container-type:inline-size}.fp{margin-block-end:var(--sp-4);border-radius:var(--r-lg);overflow:hidden;animation:fly-fp-in var(--t-overlay);border:1px solid var(--glass2-border);background-image:var(--glass2-bg);box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset)}@media(prefers-reduced-transparency:reduce){.fp{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.fp:before,.fp:after{display:none}}@media(prefers-contrast:more){.fp{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.fp:after{animation:none}}:host(.fp--anchored){--fp-shift-x: 0px;--fp-max-w: calc(100vw - 2 * var(--sp-4));position:absolute;inset-block-start:calc(100% + var(--sp-2));inset-inline-end:0;z-index:var(--z-overlay);width:min(600px,var(--fp-max-w));transform:translate(var(--fp-shift-x))}:host(.fp--floating){--fp-max-w: calc(100vw - 2 * var(--sp-4));--fp-max-h: calc(100vh - 2 * var(--sp-4));--fp-x: 0px;--fp-y: 0px;position:fixed;inset-block-start:var(--fp-y);inset-inline-start:auto;left:var(--fp-x);z-index:var(--z-overlay);width:min(600px,var(--fp-max-w));max-height:var(--fp-max-h)}:host(.fp--floating) .fp{margin-block-end:0;max-height:var(--fp-max-h);display:flex;flex-direction:column}:host(.fp--floating) .fp__body{flex:1 1 auto;min-height:0;overflow-y:auto}:host(.fp--anchored) .fp{margin-block-end:0}:host(.fp--anchored) .fp__body{max-height:min(320px,44vh);overflow-y:auto}.fp__head{display:flex;align-items:center;justify-content:space-between;padding:var(--sp-3) var(--sp-4);border-block-end:.5px solid var(--w1)}.fp__title{font-size:var(--text-xs);font-weight:var(--fw-medium);letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3)}.fp__actions{display:flex;align-items:center;gap:var(--sp-1)}.fp__clear{border:0;background:transparent;font-size:12px;color:var(--ink-3);padding:var(--sp-1) var(--sp-2);border-radius:var(--r-sm);cursor:pointer;font-family:inherit;transition:background var(--t-state),color var(--t-state)}.fp__clear:hover{color:var(--ink);background:var(--w08)}.fp__clear:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fp__close{width:24px;height:24px;border:0;background:transparent;color:var(--ink-3);cursor:pointer;border-radius:var(--r-sm);font-size:var(--text-xl);line-height:1;display:grid;place-items:center}.fp__close:hover{color:var(--ink);background:var(--w08)}.fp__close:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fp ::ng-deep [filter-status]{display:flex;flex-direction:column;gap:var(--sp-2);padding:14px var(--sp-4);border-block-end:.5px solid var(--w08)}.fp__grid{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:14px var(--sp-4);padding:var(--sp-4)}@container (width <= 1180px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(4,minmax(0,1fr))}}@media(width<=1180px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(4,minmax(0,1fr))}}@container (width <= 820px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(width<=820px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(2,minmax(0,1fr))}}.fp[data-layout=rows] .fp__grid{grid-template-columns:1fr;gap:var(--sp-2)}.fp[data-layout=rows] ::ng-deep .fld{display:grid;grid-template-columns:minmax(110px,168px) 1fr;align-items:center;gap:var(--sp-1) var(--sp-4)}.fp[data-layout=rows] ::ng-deep .fld__l{margin:0;color:var(--ink-2)}.fp[data-layout=rows] ::ng-deep .fld__hint,.fp[data-layout=rows] ::ng-deep .fld__err{grid-column:2}@container (width <= 640px){.fp[data-layout=rows] ::ng-deep .fld{grid-template-columns:1fr}.fp[data-layout=rows] ::ng-deep .fld__hint,.fp[data-layout=rows] ::ng-deep .fld__err{grid-column:1}}@media(width<=640px){.fp[data-layout=rows] ::ng-deep .fld{grid-template-columns:1fr}.fp[data-layout=rows] ::ng-deep .fld__hint,.fp[data-layout=rows] ::ng-deep .fld__err{grid-column:1}}.fp__more{border-block-start:.5px solid var(--w08)}.fp__more .fp__grid{padding-block-start:var(--sp-3)}.fp__morebar{padding:0 var(--sp-4) var(--sp-3)}.fp__morebtn{display:inline-flex;align-items:center;gap:var(--sp-1);border:0;background:transparent;padding:var(--sp-1) var(--sp-2);border-radius:var(--r-sm);font-family:inherit;font-size:var(--text-sm);font-weight:var(--fw-medium);color:var(--accent);cursor:pointer;transition:background var(--t-state)}.fp__morebtn:hover{background:var(--w08)}.fp__morebtn:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fp__moreico{width:14px;height:14px;flex-shrink:0}.fp__footer{display:flex;justify-content:flex-end;padding:var(--sp-3) var(--sp-4);border-block-start:.5px solid var(--w08)}.fp__footer button{background:var(--grad);border:0;color:var(--ink-inverse);box-shadow:var(--shadow-raised)}.fp__footer button:hover{background:var(--grad);border:0;filter:brightness(1.05);box-shadow:var(--shadow-accent)}@keyframes fly-fp-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}\n"] }]
22475
+ }], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: true }] }], titleKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleKey", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], hasMore: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasMore", required: false }] }], showSearchButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSearchButton", required: false }] }], anchored: [{ type: i0.Input, args: [{ isSignal: true, alias: "anchored", required: false }] }], anchorEl: [{ type: i0.Input, args: [{ isSignal: true, alias: "anchorEl", required: false }] }], cleared: [{ type: i0.Output, args: ["cleared"] }], closed: [{ type: i0.Output, args: ["closed"] }], searched: [{ type: i0.Output, args: ["searched"] }] } });
22250
22476
 
22251
22477
  /**
22252
22478
  * Per-column cell override for {@link FlyDataTableComponent}. Declared on an
@@ -23292,7 +23518,7 @@ class FlyAppTopbarComponent {
23292
23518
  this.rows()[index]?.nativeElement.focus();
23293
23519
  }
23294
23520
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyAppTopbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
23295
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyAppTopbarComponent, isStandalone: true, selector: "fly-app-topbar", inputs: { brandLabelKey: { classPropertyName: "brandLabelKey", publicName: "brandLabelKey", isSignal: true, isRequired: false, transformFunction: null }, sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: false, transformFunction: null }, activeKey: { classPropertyName: "activeKey", publicName: "activeKey", isSignal: true, isRequired: false, transformFunction: null }, footerLabel: { classPropertyName: "footerLabel", publicName: "footerLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { moduleSelected: "moduleSelected", brandSelected: "brandSelected" }, host: { listeners: { "document:keydown.escape": "onEscape()" }, properties: { "class.fly-topbar--docked": "docked()" } }, queries: [{ propertyName: "icons", predicate: FlyModuleIconDirective, isSignal: true }], viewQueries: [{ propertyName: "rows", predicate: ["row"], descendants: true, isSignal: true }, { propertyName: "trigger", predicate: ["trigger"], descendants: true, isSignal: true }], ngImport: i0, template: "<header class=\"fly-topbar\">\r\n <div class=\"fly-topbar__lead\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-topbar__brand\"\r\n (click)=\"brandSelected.emit()\"\r\n [attr.aria-label]=\"brandAriaLabel()\"\r\n >\r\n <span class=\"fly-topbar__brand-mark\"><ng-content select=\"[app-topbar-brand]\" /></span>\r\n @if (brandLabelKey(); as key) {\r\n <span class=\"fly-topbar__brand-name\">{{ key | translate }}</span>\r\n }\r\n </button>\r\n\r\n @if (activeModule()) {\r\n <span class=\"fly-topbar__sep\" aria-hidden=\"true\">/</span>\r\n }\r\n\r\n <div class=\"fly-topbar__anchor\" (flyClickOutside)=\"close()\" [flyClickOutsideEnabled]=\"open()\">\r\n <button\r\n #trigger\r\n type=\"button\"\r\n class=\"fly-topbar__module\"\r\n [class.fly-topbar__module--on]=\"open()\"\r\n (click)=\"toggle()\"\r\n (keydown)=\"onTriggerKeydown($event)\"\r\n aria-haspopup=\"menu\"\r\n [attr.aria-expanded]=\"open()\"\r\n [attr.aria-label]=\"triggerAriaLabel()\"\r\n >\r\n @if (activeModule(); as active) {\r\n <span class=\"fly-topbar__module-icon\">\r\n @if (iconFor(active.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (active.iconClass) {\r\n <i [class]=\"active.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n <span class=\"fly-topbar__module-label\">{{ active.labelKey | translate }}</span>\r\n } @else {\r\n <span class=\"fly-topbar__module-label fly-topbar__module-label--empty\">\r\n {{ 'ui.nav.selectModule' | translate }}\r\n </span>\r\n }\r\n <svg class=\"fly-topbar__chevron\" width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\">\r\n <polyline points=\"6 9 12 15 18 9\" />\r\n </svg>\r\n </button>\r\n\r\n @if (open()) {\r\n <div class=\"fly-topbar__pop\" role=\"menu\" [attr.aria-label]=\"switchAriaLabel()\">\r\n <div class=\"fly-topbar__cols\">\r\n @for (section of sections(); track $index) {\r\n <div class=\"fly-topbar__col\">\r\n @if (section.titleKey; as titleKey) {\r\n <div class=\"fly-topbar__col-title\">{{ titleKey | translate }}</div>\r\n }\r\n @for (mod of section.modules; track mod.key) {\r\n <button\r\n #row\r\n type=\"button\"\r\n class=\"fly-topbar__item\"\r\n [class.fly-topbar__item--on]=\"mod.key === activeKey()\"\r\n role=\"menuitem\"\r\n [disabled]=\"mod.disabled ?? false\"\r\n [attr.tabindex]=\"indexOf(mod) === focusIndex() ? 0 : -1\"\r\n [attr.aria-current]=\"mod.key === activeKey() ? 'page' : null\"\r\n (click)=\"select(mod)\"\r\n (keydown)=\"onRowKeydown($event)\"\r\n >\r\n <span class=\"fly-topbar__item-icon\">\r\n @if (iconFor(mod.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (mod.iconClass) {\r\n <i [class]=\"mod.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n <span class=\"fly-topbar__item-main\">\r\n <span class=\"fly-topbar__item-label\">{{ mod.labelKey | translate }}</span>\r\n @if (mod.descKey; as descKey) {\r\n <span class=\"fly-topbar__item-desc\">{{ descKey | translate }}</span>\r\n }\r\n </span>\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n @if (footerLabel()) {\r\n <div class=\"fly-topbar__foot\">{{ footerLabel() }}</div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"fly-topbar__actions\"><ng-content select=\"[app-topbar-actions]\" /></div>\r\n</header>\r\n", styles: [":host{display:block;position:relative;z-index:var(--z-sticky)}.fly-topbar{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-3);padding:var(--sp-2) var(--sp-3);border-block-end:1px solid var(--w1);background:var(--bg-3)}.fly-topbar__lead{display:flex;align-items:center;gap:var(--sp-2);min-inline-size:0}.fly-topbar__brand,.fly-topbar__module{display:inline-flex;align-items:center;gap:var(--sp-2);appearance:none;border:0;background:transparent;cursor:pointer;font:inherit;color:var(--ink);padding:var(--sp-1) var(--sp-2);border-radius:var(--r-md);transition:background var(--t-state)}.fly-topbar__brand:hover,.fly-topbar__module:hover{background:var(--bg-hover)}.fly-topbar__brand:focus-visible,.fly-topbar__module:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fly-topbar__brand-name{font-weight:var(--fw-semibold);white-space:nowrap}.fly-topbar__brand-mark{display:inline-flex;align-items:center;max-block-size:var(--icon-lg)}.fly-topbar__sep{color:var(--ink-4);-webkit-user-select:none;user-select:none}.fly-topbar__module{min-inline-size:0}.fly-topbar__module--on{background:var(--bg-hover)}.fly-topbar__module-label{font-weight:var(--fw-medium);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-topbar__module-label--empty{color:var(--ink-3)}.fly-topbar__module-icon,.fly-topbar__item-icon{display:inline-flex;align-items:center;justify-content:center;inline-size:var(--icon-md);block-size:var(--icon-md);flex:0 0 auto;color:var(--accent)}.fly-topbar__chevron{flex:0 0 auto;color:var(--ink-3);transition:transform var(--t-state)}.fly-topbar__module--on .fly-topbar__chevron{transform:rotate(180deg)}.fly-topbar__anchor{position:relative}:host(.fly-topbar--docked){flex:1 1 auto;min-inline-size:0;z-index:auto}:host(.fly-topbar--docked) .fly-topbar{padding:0;background:transparent;border-block-end:0}:host(.fly-topbar--docked) .fly-topbar__lead{gap:2px}:host(.fly-topbar--docked) .fly-topbar__brand,:host(.fly-topbar--docked) .fly-topbar__module{block-size:26px;padding-block:0;padding-inline:var(--sp-2);border-radius:var(--r-sm);font-size:var(--text-sm);line-height:1;color:var(--label-secondary)}:host(.fly-topbar--docked) .fly-topbar__brand:hover,:host(.fly-topbar--docked) .fly-topbar__module:hover{background:var(--w08);color:var(--label-primary)}:host(.fly-topbar--docked) .fly-topbar__module{color:var(--label-primary)}:host(.fly-topbar--docked) .fly-topbar__module--on{background:var(--w08)}:host(.fly-topbar--docked) .fly-topbar__brand-name{font-weight:var(--fw-medium)}:host(.fly-topbar--docked) .fly-topbar__module-label{font-weight:var(--fw-semibold)}:host(.fly-topbar--docked) .fly-topbar__sep{color:var(--w28)}:host(.fly-topbar--docked) .fly-topbar__module-icon{inline-size:14px;block-size:14px}:host(.fly-topbar--docked) .fly-topbar__brand-mark{max-block-size:22px}:host(.fly-topbar--docked) .fly-topbar__brand-mark ::ng-deep *{max-inline-size:22px;max-block-size:22px}.fly-topbar__pop{position:absolute;inset-block-start:calc(100% + var(--sp-1));inset-inline-start:0;z-index:var(--z-overlay);min-inline-size:260px;max-inline-size:min(680px,92vw);padding:var(--sp-3);border-radius:var(--r-lg);border:1px solid var(--glass2-border);background-image:radial-gradient(70% 60% at 50% 100%,var(--w07),transparent 70%),linear-gradient(180deg,var(--w08),transparent 20%),linear-gradient(180deg,var(--mat-menu-solid-a),var(--mat-menu-solid-b));box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset)}@media(prefers-reduced-transparency:reduce){.fly-topbar__pop{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.fly-topbar__pop:before,.fly-topbar__pop:after{display:none}}@media(prefers-contrast:more){.fly-topbar__pop{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.fly-topbar__pop:after{animation:none}}.fly-topbar__cols{display:flex;gap:var(--sp-4);align-items:flex-start}@media(width<=720px){.fly-topbar__cols{flex-direction:column;gap:var(--sp-3)}}.fly-topbar__col{display:flex;flex-direction:column;gap:2px;min-inline-size:220px;flex:1 1 0}.fly-topbar__col-title{font-size:var(--text-2xs);font-weight:var(--fw-semibold);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-3);padding:var(--sp-2) var(--sp-2) var(--sp-1)}.fly-topbar__item{display:flex;align-items:flex-start;gap:var(--sp-2);inline-size:100%;text-align:start;appearance:none;border:0;background:transparent;cursor:pointer;font:inherit;color:var(--ink);padding:var(--sp-2);border-radius:var(--r-md);transition:background var(--t-state)}.fly-topbar__item:hover:not(:disabled){background:var(--w1)}.fly-topbar__item:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-2px}.fly-topbar__item:disabled{opacity:.5;cursor:default}.fly-topbar__item--on{background:var(--accent-soft);color:var(--on-accent-soft);box-shadow:inset 2px 0 0 0 var(--accent)}.fly-topbar__item-main{display:flex;flex-direction:column;gap:1px;min-inline-size:0}.fly-topbar__item-label{font-weight:var(--fw-medium);line-height:1.3}.fly-topbar__item-desc{font-size:var(--text-xs);color:var(--ink-3);line-height:1.35}.fly-topbar__foot{margin-block-start:var(--sp-3);padding-block-start:var(--sp-2);border-block-start:.5px solid var(--w08);font-size:var(--text-2xs);color:var(--ink-4)}.fly-topbar__actions{display:flex;align-items:center;gap:var(--sp-2);flex:0 0 auto}@media(forced-colors:active){.fly-topbar__item--on{border-inline-start:2px solid CanvasText}}@media(prefers-reduced-motion:reduce){.fly-topbar__brand,.fly-topbar__module,.fly-topbar__item,.fly-topbar__chevron{transition:none}}\n"], dependencies: [{ kind: "directive", type: FlyClickOutsideDirective, selector: "[flyClickOutside]", inputs: ["flyClickOutsideEnabled"], outputs: ["flyClickOutside"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
23521
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyAppTopbarComponent, isStandalone: true, selector: "fly-app-topbar", inputs: { brandLabelKey: { classPropertyName: "brandLabelKey", publicName: "brandLabelKey", isSignal: true, isRequired: false, transformFunction: null }, sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: false, transformFunction: null }, activeKey: { classPropertyName: "activeKey", publicName: "activeKey", isSignal: true, isRequired: false, transformFunction: null }, footerLabel: { classPropertyName: "footerLabel", publicName: "footerLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { moduleSelected: "moduleSelected", brandSelected: "brandSelected" }, host: { listeners: { "document:keydown.escape": "onEscape()" }, properties: { "class.fly-topbar--docked": "docked()" } }, queries: [{ propertyName: "icons", predicate: FlyModuleIconDirective, isSignal: true }], viewQueries: [{ propertyName: "rows", predicate: ["row"], descendants: true, isSignal: true }, { propertyName: "trigger", predicate: ["trigger"], descendants: true, isSignal: true }], ngImport: i0, template: "<header class=\"fly-topbar\">\r\n <div class=\"fly-topbar__lead\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-topbar__brand\"\r\n (click)=\"brandSelected.emit()\"\r\n [attr.aria-label]=\"brandAriaLabel()\"\r\n >\r\n <span class=\"fly-topbar__brand-mark\"><ng-content select=\"[app-topbar-brand]\" /></span>\r\n @if (brandLabelKey(); as key) {\r\n <span class=\"fly-topbar__brand-name\">{{ key | translate }}</span>\r\n }\r\n </button>\r\n\r\n @if (activeModule()) {\r\n <span class=\"fly-topbar__sep\" aria-hidden=\"true\">/</span>\r\n }\r\n\r\n <div class=\"fly-topbar__anchor\" (flyClickOutside)=\"close()\" [flyClickOutsideEnabled]=\"open()\">\r\n <button\r\n #trigger\r\n type=\"button\"\r\n class=\"fly-topbar__module\"\r\n [class.fly-topbar__module--on]=\"open()\"\r\n (click)=\"toggle()\"\r\n (keydown)=\"onTriggerKeydown($event)\"\r\n aria-haspopup=\"menu\"\r\n [attr.aria-expanded]=\"open()\"\r\n [attr.aria-label]=\"triggerAriaLabel()\"\r\n >\r\n @if (activeModule(); as active) {\r\n <span class=\"fly-topbar__module-icon\">\r\n @if (iconFor(active.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (active.iconClass) {\r\n <i [class]=\"active.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n <span class=\"fly-topbar__module-label\">{{ active.labelKey | translate }}</span>\r\n } @else {\r\n <span class=\"fly-topbar__module-label fly-topbar__module-label--empty\">\r\n {{ 'ui.nav.selectModule' | translate }}\r\n </span>\r\n }\r\n <svg class=\"fly-topbar__chevron\" width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\">\r\n <polyline points=\"6 9 12 15 18 9\" />\r\n </svg>\r\n </button>\r\n\r\n @if (open()) {\r\n <div class=\"fly-topbar__pop\" role=\"menu\" [attr.aria-label]=\"switchAriaLabel()\">\r\n <div class=\"fly-topbar__cols\">\r\n @for (section of sections(); track $index) {\r\n <div class=\"fly-topbar__col\">\r\n @if (section.titleKey; as titleKey) {\r\n <div class=\"fly-topbar__col-title\">{{ titleKey | translate }}</div>\r\n }\r\n @for (mod of section.modules; track mod.key) {\r\n <button\r\n #row\r\n type=\"button\"\r\n class=\"fly-topbar__item\"\r\n [class.fly-topbar__item--on]=\"mod.key === activeKey()\"\r\n role=\"menuitem\"\r\n [disabled]=\"mod.disabled ?? false\"\r\n [attr.tabindex]=\"indexOf(mod) === focusIndex() ? 0 : -1\"\r\n [attr.aria-current]=\"mod.key === activeKey() ? 'page' : null\"\r\n (click)=\"select(mod)\"\r\n (keydown)=\"onRowKeydown($event)\"\r\n >\r\n <span class=\"fly-topbar__item-icon\">\r\n @if (iconFor(mod.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (mod.iconClass) {\r\n <i [class]=\"mod.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n <span class=\"fly-topbar__item-main\">\r\n <span class=\"fly-topbar__item-label\">{{ mod.labelKey | translate }}</span>\r\n @if (mod.descKey; as descKey) {\r\n <span class=\"fly-topbar__item-desc\">{{ descKey | translate }}</span>\r\n }\r\n </span>\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n @if (footerLabel()) {\r\n <div class=\"fly-topbar__foot\">{{ footerLabel() }}</div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"fly-topbar__actions\"><ng-content select=\"[app-topbar-actions]\" /></div>\r\n</header>\r\n", styles: [":host{display:block;position:relative;z-index:var(--z-sticky)}.fly-topbar{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-3);padding:var(--sp-2) var(--sp-3);border-block-end:1px solid var(--w1);background:var(--bg-3)}.fly-topbar__lead{display:flex;align-items:center;gap:var(--sp-2);min-inline-size:0}.fly-topbar__brand,.fly-topbar__module{display:inline-flex;align-items:center;gap:var(--sp-2);appearance:none;border:0;background:transparent;cursor:pointer;font:inherit;color:var(--ink);padding:var(--sp-1) var(--sp-2);border-radius:var(--r-md);transition:background var(--t-state)}.fly-topbar__brand:hover,.fly-topbar__module:hover{background:var(--bg-hover)}.fly-topbar__brand:focus-visible,.fly-topbar__module:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fly-topbar__brand-name{font-weight:var(--fw-semibold);white-space:nowrap}.fly-topbar__brand-mark{display:inline-flex;align-items:center;max-block-size:var(--icon-lg)}.fly-topbar__sep{color:var(--ink-4);-webkit-user-select:none;user-select:none}.fly-topbar__module{min-inline-size:0}.fly-topbar__module--on{background:var(--bg-hover)}.fly-topbar__module-label{font-weight:var(--fw-medium);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-topbar__module-label--empty{color:var(--ink-3)}.fly-topbar__module-icon,.fly-topbar__item-icon{display:inline-flex;align-items:center;justify-content:center;inline-size:var(--icon-md);block-size:var(--icon-md);flex:0 0 auto;color:var(--accent)}.fly-topbar__chevron{flex:0 0 auto;color:var(--ink-3);transition:transform var(--t-state)}.fly-topbar__module--on .fly-topbar__chevron{transform:rotate(180deg)}.fly-topbar__anchor{position:relative}:host(.fly-topbar--docked){flex:1 1 auto;min-inline-size:0;z-index:auto}:host(.fly-topbar--docked) .fly-topbar{padding:0;background:transparent;border-block-end:0}:host(.fly-topbar--docked) .fly-topbar__lead{gap:2px}:host(.fly-topbar--docked) .fly-topbar__brand,:host(.fly-topbar--docked) .fly-topbar__module{block-size:26px;padding-block:0;padding-inline:var(--sp-2);border-radius:var(--r-sm);font-size:var(--text-sm);line-height:1;color:var(--label-secondary)}:host(.fly-topbar--docked) .fly-topbar__brand:hover,:host(.fly-topbar--docked) .fly-topbar__module:hover{background:var(--w08);color:var(--label-primary)}:host(.fly-topbar--docked) .fly-topbar__module{color:var(--label-primary)}:host(.fly-topbar--docked) .fly-topbar__module--on{background:var(--w08)}:host(.fly-topbar--docked) .fly-topbar__brand-name{font-weight:var(--fw-medium)}:host(.fly-topbar--docked) .fly-topbar__module-label{font-weight:var(--fw-semibold)}:host(.fly-topbar--docked) .fly-topbar__sep{color:var(--w28)}:host(.fly-topbar--docked) .fly-topbar__module-icon{inline-size:14px;block-size:14px}:host(.fly-topbar--docked) .fly-topbar__brand-mark{max-block-size:22px}:host(.fly-topbar--docked) .fly-topbar__brand-mark ::ng-deep *{max-inline-size:22px;max-block-size:22px}.fly-topbar__pop{position:absolute;inset-block-start:calc(100% + var(--sp-1));inset-inline-start:0;z-index:var(--z-overlay);min-inline-size:260px;max-inline-size:min(680px,92vw);padding:var(--sp-3);border-radius:var(--r-lg);border:1px solid var(--glass2-border);background-image:radial-gradient(70% 60% at 50% 100%,var(--w07),transparent 70%),linear-gradient(180deg,var(--w08),transparent 20%),linear-gradient(180deg,var(--mat-menu-solid-a),var(--mat-menu-solid-b));box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset)}@media(prefers-reduced-transparency:reduce){.fly-topbar__pop{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.fly-topbar__pop:before,.fly-topbar__pop:after{display:none}}@media(prefers-contrast:more){.fly-topbar__pop{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.fly-topbar__pop:after{animation:none}}.fly-topbar__cols{display:flex;gap:var(--sp-4);align-items:flex-start}@media(width<=720px){.fly-topbar__cols{flex-direction:column;gap:var(--sp-3)}}.fly-topbar__col{display:flex;flex-direction:column;gap:2px;min-inline-size:220px;flex:1 1 0}.fly-topbar__col-title{font-size:var(--text-2xs);font-weight:var(--fw-semibold);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-3);padding:var(--sp-2) var(--sp-2) var(--sp-1)}.fly-topbar__item{display:flex;align-items:flex-start;gap:var(--sp-2);inline-size:100%;text-align:start;appearance:none;border:0;background:transparent;cursor:pointer;font:inherit;color:var(--ink);padding:var(--sp-2);border-radius:var(--r-md);transition:background var(--t-state)}.fly-topbar__item:hover:not(:disabled){background:var(--w1)}.fly-topbar__item:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-2px}.fly-topbar__item:disabled{opacity:.5;cursor:default}.fly-topbar__item--on{background:var(--accent-soft);color:var(--on-accent-soft);box-shadow:inset 2px 0 0 0 var(--accent)}.fly-topbar__item-main{display:flex;flex-direction:column;gap:1px;min-inline-size:0}.fly-topbar__item-label{font-weight:var(--fw-medium);line-height:1.3}.fly-topbar__item-desc{font-size:var(--text-xs);color:var(--ink-3);line-height:1.35}.fly-topbar__foot{margin-block-start:var(--sp-3);padding-block-start:var(--sp-2);border-block-start:.5px solid var(--w08);font-size:var(--text-2xs);color:var(--ink-4)}.fly-topbar__actions{display:flex;align-items:center;gap:var(--sp-2);flex:0 0 auto}@media(forced-colors:active){.fly-topbar__item--on{border-inline-start:2px solid CanvasText}}@media(prefers-reduced-motion:reduce){.fly-topbar__brand,.fly-topbar__module,.fly-topbar__item,.fly-topbar__chevron{transition:none}}\n"], dependencies: [{ kind: "directive", type: FlyClickOutsideDirective, selector: "[flyClickOutside]", inputs: ["flyClickOutsideEnabled", "flyClickOutsideIgnore"], outputs: ["flyClickOutside"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
23296
23522
  }
23297
23523
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyAppTopbarComponent, decorators: [{
23298
23524
  type: Component,
@@ -23942,13 +24168,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
23942
24168
  }], propDecorators: { checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], labelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelKey", required: false }] }], hintKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "hintKey", required: false }] }] } });
23943
24169
 
23944
24170
  /**
23945
- * Segmented radiogroup — one anatomy for form segmented controls (the
23946
- * llm-throttle level picker) and list status-pill filters (the legacy
23947
- * `.signals-page-statusfilter`, which is also the visual source).
24171
+ * Segmented control — one anatomy for form segmented controls (the llm-throttle
24172
+ * level picker), list status-pill filters (the legacy `.signals-page-statusfilter`,
24173
+ * which is also the visual source), and the canon filter-panel status row.
23948
24174
  *
23949
- * a11y: `role="radiogroup"` + `role="radio"` segments, roving tabindex
23950
- * (selected segment is the tab stop), RTL-aware arrow-key navigation with
23951
- * wrap-around; arrows move both selection and focus.
24175
+ * ## Two selection models
24176
+ * Single-select (default) is a `role="radiogroup"` over `role="radio"` segments,
24177
+ * driven by {@link value}. Multi-select ({@link multiple}) is a `role="group"` of
24178
+ * `aria-pressed` toggle buttons, driven by {@link values}. Both are here rather than
24179
+ * split across two components because everything a reader has to learn — the option
24180
+ * list, the count badge, the two variants, the RTL handling — is shared, and a filter
24181
+ * row routinely needs the multi-select one while looking identical.
24182
+ *
24183
+ * The a11y models genuinely differ and the branch is not cosmetic: a radiogroup is
24184
+ * ONE tab stop with arrow keys moving the selection, while a group of toggle buttons
24185
+ * is individually tabbable and arrow keys must not change anything (moving selection
24186
+ * on arrow would silently toggle filters as a keyboard user walked the row).
24187
+ *
24188
+ * a11y: roving tabindex in single-select (the selected segment is the tab stop),
24189
+ * RTL-aware arrow-key navigation with wrap-around; arrows move both selection and
24190
+ * focus. Multi-select leaves the native tab order alone and binds no arrow keys.
23952
24191
  */
23953
24192
  class FlySegmentedComponent {
23954
24193
  i18n = inject(I18nService);
@@ -23959,17 +24198,48 @@ class FlySegmentedComponent {
23959
24198
  value = model(undefined, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
23960
24199
  disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
23961
24200
  ariaLabelKey = input(...(ngDevMode ? [undefined, { debugName: "ariaLabelKey" }] : /* istanbul ignore next */ []));
24201
+ /** See {@link SegmentedVariant}. */
24202
+ variant = input('track', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
24203
+ /**
24204
+ * Switch to multi-select: every pressed option is carried in {@link values} and
24205
+ * clicking one toggles it. {@link value} is ignored in this mode.
24206
+ *
24207
+ * A separate model rather than widening `value` to `string | string[]`: a consumer
24208
+ * would then have to narrow on every read, and every EXISTING consumer's
24209
+ * `[(value)]` binding would stop type-checking — a ds-compat MAJOR for a feature
24210
+ * none of them asked for.
24211
+ */
24212
+ multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : /* istanbul ignore next */ []));
24213
+ /**
24214
+ * The pressed options in {@link multiple} mode. Order is the consumer's; this
24215
+ * component appends on select and filters on deselect, so a caller that treats it
24216
+ * as a set is unaffected and one that treats it as a list keeps its ordering.
24217
+ */
24218
+ values = model([], ...(ngDevMode ? [{ debugName: "values" }] : /* istanbul ignore next */ []));
23962
24219
  selectedIndex = computed(() => this.options().findIndex(o => o.value === this.value()), ...(ngDevMode ? [{ debugName: "selectedIndex" }] : /* istanbul ignore next */ []));
24220
+ /** Selected in whichever model is active. */
24221
+ isOn(value) {
24222
+ return this.multiple() ? this.values().includes(value) : this.value() === value;
24223
+ }
23963
24224
  tabIndexFor(index) {
23964
24225
  const selected = this.selectedIndex();
23965
24226
  return index === (selected >= 0 ? selected : 0) ? 0 : -1;
23966
24227
  }
23967
24228
  select(value) {
23968
- if (!this.disabled())
24229
+ if (this.disabled())
24230
+ return;
24231
+ if (!this.multiple()) {
23969
24232
  this.value.set(value);
24233
+ return;
24234
+ }
24235
+ this.values.update(current => current.includes(value) ? current.filter(v => v !== value) : [...current, value]);
23970
24236
  }
23971
24237
  onKey(event) {
23972
- if (this.disabled())
24238
+ // Multi-select is a group of toggle buttons, each its own tab stop: arrows must
24239
+ // stay unbound. Wiring them the way the radiogroup does would move selection as
24240
+ // well as focus, so walking the row with the keyboard would toggle every filter
24241
+ // it passed through.
24242
+ if (this.disabled() || this.multiple())
23973
24243
  return;
23974
24244
  const next = nextSegmentIndex(this.selectedIndex(), event.key, this.options().length, this.i18n.isRtl());
23975
24245
  if (next === null)
@@ -23979,64 +24249,68 @@ class FlySegmentedComponent {
23979
24249
  this.el.nativeElement.querySelectorAll('[role="radio"]')[next]?.focus();
23980
24250
  }
23981
24251
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlySegmentedComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
23982
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlySegmentedComponent, isStandalone: true, selector: "fly-segmented", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelKey: { classPropertyName: "ariaLabelKey", publicName: "ariaLabelKey", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, ngImport: i0, template: `
23983
- <div
23984
- class="seg"
23985
- role="radiogroup"
23986
- tabindex="-1"
23987
- [attr.aria-label]="ariaLabelKey() ? (ariaLabelKey()! | translate) : null"
23988
- (keydown)="onKey($event)"
23989
- >
23990
- @for (opt of options(); track opt.value) {
23991
- <button
23992
- type="button"
23993
- class="seg__btn"
23994
- role="radio"
23995
- [class.on]="value() === opt.value"
23996
- [attr.aria-checked]="value() === opt.value"
23997
- [attr.tabindex]="tabIndexFor($index)"
23998
- [disabled]="disabled()"
23999
- (click)="select(opt.value)"
24000
- >
24001
- {{ opt.labelKey | translate }}
24002
- @if (opt.count !== undefined) {
24003
- <span class="seg__count">{{ opt.count }}</span>
24004
- }
24005
- </button>
24006
- }
24007
- </div>
24008
- `, isInline: true, styles: [":host{display:inline-flex}.seg{display:flex;flex-wrap:wrap;gap:3px;padding:3px;background:var(--w07);border:1px solid var(--w08);border-radius:99px}.seg__btn{display:inline-flex;align-items:center;gap:6px;min-block-size:28px;padding:0 13px;border:0;background:transparent;color:var(--w55);font:500 12.5px/1.3 var(--font-family);font-family:inherit;border-radius:99px;cursor:pointer;white-space:nowrap;transition:background var(--nova-duration-hover) var(--nova-ease-micro),color var(--nova-duration-hover) var(--nova-ease-micro)}.seg__btn:hover{color:var(--w85)}.seg__btn:focus-visible{outline:3px solid var(--accent);outline-offset:3px}.seg__btn:disabled{opacity:.38;filter:saturate(.6);cursor:not-allowed}.seg__btn.on{background:var(--w14);color:var(--w95);font-weight:600;box-shadow:var(--shadow-card),var(--glass2-inset)}.seg__count{font-family:var(--font-mono);font-variant-numeric:tabular-nums;font-size:10.5px;padding:1px 5px;background:var(--w1);border-radius:4px;color:var(--w42)}.seg__btn.on .seg__count{color:var(--w55)}@media(pointer:coarse){.seg__btn{min-block-size:44px}}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
24252
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlySegmentedComponent, isStandalone: true, selector: "fly-segmented", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelKey: { classPropertyName: "ariaLabelKey", publicName: "ariaLabelKey", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, values: { classPropertyName: "values", publicName: "values", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", values: "valuesChange" }, ngImport: i0, template: `
24253
+ <div
24254
+ class="seg"
24255
+ [attr.data-variant]="variant()"
24256
+ [attr.role]="multiple() ? 'group' : 'radiogroup'"
24257
+ tabindex="-1"
24258
+ [attr.aria-label]="ariaLabelKey() ? (ariaLabelKey()! | translate) : null"
24259
+ (keydown)="onKey($event)"
24260
+ >
24261
+ @for (opt of options(); track opt.value) {
24262
+ <button
24263
+ type="button"
24264
+ class="seg__btn"
24265
+ [attr.role]="multiple() ? null : 'radio'"
24266
+ [class.on]="isOn(opt.value)"
24267
+ [attr.aria-checked]="multiple() ? null : isOn(opt.value)"
24268
+ [attr.aria-pressed]="multiple() ? isOn(opt.value) : null"
24269
+ [attr.tabindex]="multiple() ? null : tabIndexFor($index)"
24270
+ [disabled]="disabled()"
24271
+ (click)="select(opt.value)"
24272
+ >
24273
+ {{ opt.labelKey | translate }}
24274
+ @if (opt.count !== undefined) {
24275
+ <span class="seg__count">{{ opt.count }}</span>
24276
+ }
24277
+ </button>
24278
+ }
24279
+ </div>
24280
+ `, isInline: true, styles: [":host{display:inline-flex}.seg{display:flex;flex-wrap:wrap;gap:3px;padding:3px;background:var(--w07);border:1px solid var(--w08);border-radius:99px}.seg[data-variant=pills]{gap:6px;padding:0;background:transparent;border:0;border-radius:0}.seg__btn{display:inline-flex;align-items:center;gap:6px;min-block-size:28px;padding:0 13px;border:0;background:transparent;color:var(--w55);font:500 12.5px/1.3 var(--font-family);font-family:inherit;border-radius:99px;cursor:pointer;white-space:nowrap;transition:background var(--nova-duration-hover) var(--nova-ease-micro),color var(--nova-duration-hover) var(--nova-ease-micro)}.seg__btn:hover{color:var(--w85)}.seg__btn:focus-visible{outline:3px solid var(--accent);outline-offset:3px}.seg__btn:disabled{opacity:.38;filter:saturate(.6);cursor:not-allowed}.seg__btn.on{background:var(--w14);color:var(--w95);font-weight:600;box-shadow:var(--shadow-card),var(--glass2-inset)}.seg__count{font-family:var(--font-mono);font-variant-numeric:tabular-nums;font-size:10.5px;padding:1px 5px;background:var(--w1);border-radius:4px;color:var(--w42)}.seg__btn.on .seg__count{color:var(--w55)}.seg[data-variant=pills] .seg__btn{min-block-size:30px;padding:0 12px;border:.5px solid var(--w12);background:var(--w04);color:var(--w7);border-radius:99px;transition:background var(--nova-duration-hover) var(--nova-ease-micro),color var(--nova-duration-hover) var(--nova-ease-micro),border-color var(--nova-duration-hover) var(--nova-ease-micro)}.seg[data-variant=pills] .seg__btn:hover:not(.on){background:var(--w08);border-color:var(--w2);color:var(--ink)}.seg[data-variant=pills] .seg__btn.on{border-color:transparent;background:var(--accent);color:var(--on-accent);font-weight:500;box-shadow:none}.seg[data-variant=pills] .seg__count{padding:2px 6px;border-radius:7px;font-family:var(--font-family);font-size:10.5px;font-weight:var(--fw-semibold);color:inherit}.seg[data-variant=pills] .seg__btn.on .seg__count{background:var(--w22);color:inherit}@media(pointer:coarse){.seg__btn,.seg[data-variant=pills] .seg__btn{min-block-size:44px}}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
24009
24281
  }
24010
24282
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlySegmentedComponent, decorators: [{
24011
24283
  type: Component,
24012
- args: [{ selector: 'fly-segmented', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: `
24013
- <div
24014
- class="seg"
24015
- role="radiogroup"
24016
- tabindex="-1"
24017
- [attr.aria-label]="ariaLabelKey() ? (ariaLabelKey()! | translate) : null"
24018
- (keydown)="onKey($event)"
24019
- >
24020
- @for (opt of options(); track opt.value) {
24021
- <button
24022
- type="button"
24023
- class="seg__btn"
24024
- role="radio"
24025
- [class.on]="value() === opt.value"
24026
- [attr.aria-checked]="value() === opt.value"
24027
- [attr.tabindex]="tabIndexFor($index)"
24028
- [disabled]="disabled()"
24029
- (click)="select(opt.value)"
24030
- >
24031
- {{ opt.labelKey | translate }}
24032
- @if (opt.count !== undefined) {
24033
- <span class="seg__count">{{ opt.count }}</span>
24034
- }
24035
- </button>
24036
- }
24037
- </div>
24038
- `, styles: [":host{display:inline-flex}.seg{display:flex;flex-wrap:wrap;gap:3px;padding:3px;background:var(--w07);border:1px solid var(--w08);border-radius:99px}.seg__btn{display:inline-flex;align-items:center;gap:6px;min-block-size:28px;padding:0 13px;border:0;background:transparent;color:var(--w55);font:500 12.5px/1.3 var(--font-family);font-family:inherit;border-radius:99px;cursor:pointer;white-space:nowrap;transition:background var(--nova-duration-hover) var(--nova-ease-micro),color var(--nova-duration-hover) var(--nova-ease-micro)}.seg__btn:hover{color:var(--w85)}.seg__btn:focus-visible{outline:3px solid var(--accent);outline-offset:3px}.seg__btn:disabled{opacity:.38;filter:saturate(.6);cursor:not-allowed}.seg__btn.on{background:var(--w14);color:var(--w95);font-weight:600;box-shadow:var(--shadow-card),var(--glass2-inset)}.seg__count{font-family:var(--font-mono);font-variant-numeric:tabular-nums;font-size:10.5px;padding:1px 5px;background:var(--w1);border-radius:4px;color:var(--w42)}.seg__btn.on .seg__count{color:var(--w55)}@media(pointer:coarse){.seg__btn{min-block-size:44px}}\n"] }]
24039
- }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], ariaLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelKey", required: false }] }] } });
24284
+ args: [{ selector: 'fly-segmented', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: `
24285
+ <div
24286
+ class="seg"
24287
+ [attr.data-variant]="variant()"
24288
+ [attr.role]="multiple() ? 'group' : 'radiogroup'"
24289
+ tabindex="-1"
24290
+ [attr.aria-label]="ariaLabelKey() ? (ariaLabelKey()! | translate) : null"
24291
+ (keydown)="onKey($event)"
24292
+ >
24293
+ @for (opt of options(); track opt.value) {
24294
+ <button
24295
+ type="button"
24296
+ class="seg__btn"
24297
+ [attr.role]="multiple() ? null : 'radio'"
24298
+ [class.on]="isOn(opt.value)"
24299
+ [attr.aria-checked]="multiple() ? null : isOn(opt.value)"
24300
+ [attr.aria-pressed]="multiple() ? isOn(opt.value) : null"
24301
+ [attr.tabindex]="multiple() ? null : tabIndexFor($index)"
24302
+ [disabled]="disabled()"
24303
+ (click)="select(opt.value)"
24304
+ >
24305
+ {{ opt.labelKey | translate }}
24306
+ @if (opt.count !== undefined) {
24307
+ <span class="seg__count">{{ opt.count }}</span>
24308
+ }
24309
+ </button>
24310
+ }
24311
+ </div>
24312
+ `, styles: [":host{display:inline-flex}.seg{display:flex;flex-wrap:wrap;gap:3px;padding:3px;background:var(--w07);border:1px solid var(--w08);border-radius:99px}.seg[data-variant=pills]{gap:6px;padding:0;background:transparent;border:0;border-radius:0}.seg__btn{display:inline-flex;align-items:center;gap:6px;min-block-size:28px;padding:0 13px;border:0;background:transparent;color:var(--w55);font:500 12.5px/1.3 var(--font-family);font-family:inherit;border-radius:99px;cursor:pointer;white-space:nowrap;transition:background var(--nova-duration-hover) var(--nova-ease-micro),color var(--nova-duration-hover) var(--nova-ease-micro)}.seg__btn:hover{color:var(--w85)}.seg__btn:focus-visible{outline:3px solid var(--accent);outline-offset:3px}.seg__btn:disabled{opacity:.38;filter:saturate(.6);cursor:not-allowed}.seg__btn.on{background:var(--w14);color:var(--w95);font-weight:600;box-shadow:var(--shadow-card),var(--glass2-inset)}.seg__count{font-family:var(--font-mono);font-variant-numeric:tabular-nums;font-size:10.5px;padding:1px 5px;background:var(--w1);border-radius:4px;color:var(--w42)}.seg__btn.on .seg__count{color:var(--w55)}.seg[data-variant=pills] .seg__btn{min-block-size:30px;padding:0 12px;border:.5px solid var(--w12);background:var(--w04);color:var(--w7);border-radius:99px;transition:background var(--nova-duration-hover) var(--nova-ease-micro),color var(--nova-duration-hover) var(--nova-ease-micro),border-color var(--nova-duration-hover) var(--nova-ease-micro)}.seg[data-variant=pills] .seg__btn:hover:not(.on){background:var(--w08);border-color:var(--w2);color:var(--ink)}.seg[data-variant=pills] .seg__btn.on{border-color:transparent;background:var(--accent);color:var(--on-accent);font-weight:500;box-shadow:none}.seg[data-variant=pills] .seg__count{padding:2px 6px;border-radius:7px;font-family:var(--font-family);font-size:10.5px;font-weight:var(--fw-semibold);color:inherit}.seg[data-variant=pills] .seg__btn.on .seg__count{background:var(--w22);color:inherit}@media(pointer:coarse){.seg__btn,.seg[data-variant=pills] .seg__btn{min-block-size:44px}}\n"] }]
24313
+ }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], ariaLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelKey", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], values: [{ type: i0.Input, args: [{ isSignal: true, alias: "values", required: false }] }, { type: i0.Output, args: ["valuesChange"] }] } });
24040
24314
 
24041
24315
  /**
24042
24316
  * Switch toggle (`role="switch"`). No legacy visual source existed — minimal