@flyos/design-system 3.1.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.
|
|
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
|
|
@@ -1498,6 +1498,14 @@ const DS_BASELINE_LOCALES = {
|
|
|
1498
1498
|
'gantt.aria.resize_start': 'Resize start date',
|
|
1499
1499
|
'gantt.aria.resize_end': 'Resize end date',
|
|
1500
1500
|
'gantt.aria.link_handle': 'Drag to link from {{label}}',
|
|
1501
|
+
'gantt.aria.link_handle_start': 'Drag to link from the start of {{label}}',
|
|
1502
|
+
'gantt.aria.label_resize': 'Resize the label column',
|
|
1503
|
+
'gantt.aria.link': '{{type}} dependency: {{from}} to {{to}}',
|
|
1504
|
+
'gantt.aria.delete_link': 'Delete dependency',
|
|
1505
|
+
'gantt.link_type.fs': 'Finish-to-start',
|
|
1506
|
+
'gantt.link_type.ss': 'Start-to-start',
|
|
1507
|
+
'gantt.link_type.ff': 'Finish-to-finish',
|
|
1508
|
+
'gantt.link_type.sf': 'Start-to-finish',
|
|
1501
1509
|
// dynamic-form (fly-dynamic-form / fly-survey-form)
|
|
1502
1510
|
'form.required_error': 'This field is required.',
|
|
1503
1511
|
'form.yes': 'Yes',
|
|
@@ -1880,6 +1888,14 @@ const DS_BASELINE_LOCALES = {
|
|
|
1880
1888
|
'gantt.aria.resize_start': 'تغيير تاريخ البدء',
|
|
1881
1889
|
'gantt.aria.resize_end': 'تغيير تاريخ الانتهاء',
|
|
1882
1890
|
'gantt.aria.link_handle': 'اسحب للربط من {{label}}',
|
|
1891
|
+
'gantt.aria.link_handle_start': 'اسحب للربط من بداية {{label}}',
|
|
1892
|
+
'gantt.aria.label_resize': 'تغيير عرض عمود التسميات',
|
|
1893
|
+
'gantt.aria.link': 'ارتباط {{type}}: من {{from}} إلى {{to}}',
|
|
1894
|
+
'gantt.aria.delete_link': 'حذف الارتباط',
|
|
1895
|
+
'gantt.link_type.fs': 'من النهاية إلى البداية',
|
|
1896
|
+
'gantt.link_type.ss': 'من البداية إلى البداية',
|
|
1897
|
+
'gantt.link_type.ff': 'من النهاية إلى النهاية',
|
|
1898
|
+
'gantt.link_type.sf': 'من البداية إلى النهاية',
|
|
1883
1899
|
// dynamic-form (fly-dynamic-form / fly-survey-form)
|
|
1884
1900
|
'form.required_error': 'هذا الحقل مطلوب.',
|
|
1885
1901
|
'form.yes': 'نعم',
|
|
@@ -2262,6 +2278,14 @@ const DS_BASELINE_LOCALES = {
|
|
|
2262
2278
|
'gantt.aria.resize_start': 'Redimensionner la date de début',
|
|
2263
2279
|
'gantt.aria.resize_end': 'Redimensionner la date de fin',
|
|
2264
2280
|
'gantt.aria.link_handle': 'Glisser pour lier depuis {{label}}',
|
|
2281
|
+
'gantt.aria.link_handle_start': 'Glisser pour lier depuis le début de {{label}}',
|
|
2282
|
+
'gantt.aria.label_resize': 'Redimensionner la colonne des libellés',
|
|
2283
|
+
'gantt.aria.link': 'Liaison {{type}} : de {{from}} à {{to}}',
|
|
2284
|
+
'gantt.aria.delete_link': 'Supprimer la liaison',
|
|
2285
|
+
'gantt.link_type.fs': 'Fin à début',
|
|
2286
|
+
'gantt.link_type.ss': 'Début à début',
|
|
2287
|
+
'gantt.link_type.ff': 'Fin à fin',
|
|
2288
|
+
'gantt.link_type.sf': 'Début à fin',
|
|
2265
2289
|
// dynamic-form (fly-dynamic-form / fly-survey-form)
|
|
2266
2290
|
'form.required_error': 'Ce champ est obligatoire.',
|
|
2267
2291
|
'form.yes': 'Oui',
|
|
@@ -2643,6 +2667,14 @@ const DS_BASELINE_LOCALES = {
|
|
|
2643
2667
|
'gantt.aria.resize_start': 'آغاز کی تاریخ تبدیل کریں',
|
|
2644
2668
|
'gantt.aria.resize_end': 'اختتام کی تاریخ تبدیل کریں',
|
|
2645
2669
|
'gantt.aria.link_handle': '{{label}} سے لنک کرنے کے لیے گھسیٹیں',
|
|
2670
|
+
'gantt.aria.link_handle_start': '{{label}} کے آغاز سے لنک کرنے کے لیے گھسیٹیں',
|
|
2671
|
+
'gantt.aria.label_resize': 'لیبل کالم کی چوڑائی تبدیل کریں',
|
|
2672
|
+
'gantt.aria.link': '{{type}} انحصار: {{from}} سے {{to}} تک',
|
|
2673
|
+
'gantt.aria.delete_link': 'انحصار حذف کریں',
|
|
2674
|
+
'gantt.link_type.fs': 'اختتام سے آغاز',
|
|
2675
|
+
'gantt.link_type.ss': 'آغاز سے آغاز',
|
|
2676
|
+
'gantt.link_type.ff': 'اختتام سے اختتام',
|
|
2677
|
+
'gantt.link_type.sf': 'آغاز سے اختتام',
|
|
2646
2678
|
// dynamic-form (fly-dynamic-form / fly-survey-form)
|
|
2647
2679
|
'form.required_error': 'یہ خانہ لازمی ہے۔',
|
|
2648
2680
|
'form.yes': 'ہاں',
|
|
@@ -6145,6 +6177,24 @@ class FlyClickOutsideDirective {
|
|
|
6145
6177
|
* Defaults to always-on for an always-mounted consumer.
|
|
6146
6178
|
*/
|
|
6147
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' });
|
|
6148
6198
|
/** Emitted once when a pointer press lands outside the host while enabled. */
|
|
6149
6199
|
flyClickOutside = output();
|
|
6150
6200
|
// `pointerdown` (not `click`): fires at the very start of the gesture — before focus
|
|
@@ -6160,14 +6210,18 @@ class FlyClickOutsideDirective {
|
|
|
6160
6210
|
// even if the target detaches mid-handler (a menu item that removes its own row),
|
|
6161
6211
|
// and it pierces shadow roots. Fall back to `contains` for any UA without it.
|
|
6162
6212
|
const path = event.composedPath?.();
|
|
6163
|
-
const
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
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();
|
|
6168
6222
|
}
|
|
6169
6223
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyClickOutsideDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
6170
|
-
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 });
|
|
6171
6225
|
}
|
|
6172
6226
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyClickOutsideDirective, decorators: [{
|
|
6173
6227
|
type: Directive,
|
|
@@ -6175,7 +6229,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
|
|
|
6175
6229
|
selector: '[flyClickOutside]',
|
|
6176
6230
|
standalone: true,
|
|
6177
6231
|
}]
|
|
6178
|
-
}], 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: [{
|
|
6179
6233
|
type: HostListener,
|
|
6180
6234
|
args: ['document:pointerdown', ['$event']]
|
|
6181
6235
|
}] } });
|
|
@@ -7825,7 +7879,7 @@ class FlyEmojiPickerComponent {
|
|
|
7825
7879
|
this.selectionChange.emit(next);
|
|
7826
7880
|
}
|
|
7827
7881
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyEmojiPickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
7828
|
-
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 });
|
|
7829
7883
|
}
|
|
7830
7884
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyEmojiPickerComponent, decorators: [{
|
|
7831
7885
|
type: Component,
|
|
@@ -12422,6 +12476,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
|
|
|
12422
12476
|
}, template: "<!-- \u2500\u2500 Frequency selector \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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-cron__row\">\r\n <label class=\"fly-cron__label\" [attr.for]=\"modeSelectId\">{{ 'cron.frequency_label' | translate }}</label>\r\n <!-- Selection is synced imperatively by an effect() (see the component) \u2014 a [value] /\r\n [selected] binding is applied before the @for options exist on cold render and falls\r\n back to the first option. The #modeSelect ref is what the effect drives. -->\r\n <select\r\n #modeSelect\r\n class=\"fly-cron__select\"\r\n [id]=\"modeSelectId\"\r\n [disabled]=\"effectiveDisabled()\"\r\n (change)=\"onModeChange($any($event.target).value)\"\r\n >\r\n @for (m of availableModes(); track m) {\r\n <option [value]=\"m\">{{ ('cron.mode.' + m) | translate }}</option>\r\n }\r\n </select>\r\n</div>\r\n\r\n<!-- \u2500\u2500 Mode-specific editor \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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@switch (mode()) {\r\n @case ('minutes') {\r\n <div class=\"fly-cron__row\">\r\n <span class=\"fly-cron__label\">{{ 'cron.every_label' | translate }}</span>\r\n <input\r\n class=\"fly-cron__num\"\r\n type=\"number\"\r\n min=\"1\"\r\n max=\"59\"\r\n [value]=\"everyN()\"\r\n [disabled]=\"effectiveDisabled()\"\r\n (input)=\"onEveryN($any($event.target).value)\"\r\n />\r\n <span class=\"fly-cron__suffix\">{{ 'cron.unit_minutes' | translate }}</span>\r\n </div>\r\n }\r\n\r\n @case ('hourly') {\r\n <div class=\"fly-cron__row fly-cron__row--wrap\">\r\n <span class=\"fly-cron__label\">{{ 'cron.every_label' | translate }}</span>\r\n <input\r\n class=\"fly-cron__num\"\r\n type=\"number\"\r\n min=\"1\"\r\n max=\"23\"\r\n [value]=\"everyN()\"\r\n [disabled]=\"effectiveDisabled()\"\r\n (input)=\"onEveryN($any($event.target).value)\"\r\n />\r\n <span class=\"fly-cron__suffix\">{{ 'cron.unit_hours' | translate }}</span>\r\n <span class=\"fly-cron__suffix\">{{ 'cron.at_minute_label' | translate }}</span>\r\n <input\r\n class=\"fly-cron__num\"\r\n type=\"number\"\r\n min=\"0\"\r\n max=\"59\"\r\n [value]=\"minute()\"\r\n [disabled]=\"effectiveDisabled()\"\r\n (input)=\"onMinute($any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n @case ('daily') {\r\n <div class=\"fly-cron__row\">\r\n <label class=\"fly-cron__label\" [attr.for]=\"timeInputId\">{{ 'cron.time_label' | translate }}</label>\r\n <input\r\n class=\"fly-cron__time\"\r\n type=\"time\"\r\n [id]=\"timeInputId\"\r\n [value]=\"timeValue()\"\r\n [disabled]=\"effectiveDisabled()\"\r\n (input)=\"onTime($any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n @case ('weekly') {\r\n <div class=\"fly-cron__row fly-cron__row--wrap\">\r\n <span class=\"fly-cron__label\">{{ 'cron.on_days_label' | translate }}</span>\r\n <div class=\"fly-cron__weekdays\" role=\"group\" [attr.aria-label]=\"'cron.on_days_label' | translate\">\r\n @for (d of weekdayOrder(); track d) {\r\n <button\r\n type=\"button\"\r\n role=\"checkbox\"\r\n class=\"fly-cron__chip\"\r\n [class.fly-cron__chip--on]=\"isWeekday(d)\"\r\n [attr.aria-checked]=\"isWeekday(d)\"\r\n [disabled]=\"effectiveDisabled()\"\r\n (click)=\"toggleWeekday(d)\"\r\n >{{ ('cron.weekday_short.' + d) | translate }}</button>\r\n }\r\n </div>\r\n </div>\r\n <div class=\"fly-cron__row\">\r\n <label class=\"fly-cron__label\" [attr.for]=\"timeInputId\">{{ 'cron.time_label' | translate }}</label>\r\n <input\r\n class=\"fly-cron__time\"\r\n type=\"time\"\r\n [id]=\"timeInputId\"\r\n [value]=\"timeValue()\"\r\n [disabled]=\"effectiveDisabled()\"\r\n (input)=\"onTime($any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n @case ('monthly') {\r\n <div class=\"fly-cron__row fly-cron__row--wrap\">\r\n <span class=\"fly-cron__label\">{{ 'cron.day_of_month_label' | translate }}</span>\r\n <input\r\n class=\"fly-cron__num\"\r\n type=\"number\"\r\n min=\"1\"\r\n max=\"31\"\r\n [value]=\"dayOfMonth()\"\r\n [disabled]=\"effectiveDisabled()\"\r\n (input)=\"onDayOfMonth($any($event.target).value)\"\r\n />\r\n <label class=\"fly-cron__label\" [attr.for]=\"timeInputId\">{{ 'cron.time_label' | translate }}</label>\r\n <input\r\n class=\"fly-cron__time\"\r\n type=\"time\"\r\n [id]=\"timeInputId\"\r\n [value]=\"timeValue()\"\r\n [disabled]=\"effectiveDisabled()\"\r\n (input)=\"onTime($any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n @case ('advanced') {\r\n <div class=\"fly-cron__advanced\">\r\n @for (f of advancedFieldDefs(); track f.index) {\r\n <div class=\"fly-cron__field\">\r\n <label class=\"fly-cron__field-label\" [attr.for]=\"fieldInputId(f.index)\">{{ f.labelKey | translate }}</label>\r\n <input\r\n [id]=\"fieldInputId(f.index)\"\r\n class=\"fly-cron__field-input fly-cron__mono\"\r\n type=\"text\"\r\n inputmode=\"text\"\r\n autocomplete=\"off\"\r\n spellcheck=\"false\"\r\n placeholder=\"*\"\r\n [value]=\"fields()[f.index]\"\r\n [disabled]=\"effectiveDisabled()\"\r\n [attr.aria-invalid]=\"!fieldValid(f.index)\"\r\n [attr.aria-label]=\"f.labelKey | translate\"\r\n (input)=\"onFieldInput(f.index, $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n <p class=\"fly-cron__hint\">{{ 'cron.advanced.hint' | translate }}</p>\r\n }\r\n}\r\n\r\n<!-- \u2500\u2500 Summary + resulting expression \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 (mode() !== 'none') {\r\n <div class=\"fly-cron__summary\" [class.fly-cron__summary--error]=\"!valid()\" aria-live=\"polite\">\r\n <span class=\"fly-cron__summary-text\">{{ summary() }}</span>\r\n @if (expression(); as expr) {\r\n <code class=\"fly-cron__expr fly-cron__mono\">{{ expr }}</code>\r\n }\r\n </div>\r\n}\r\n\r\n@if (errorKey(); as ek) {\r\n <p class=\"fly-cron__error\" role=\"alert\">{{ ek | translate }}</p>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--surface-card, #fff);--_surface-hover: var(--surface-hover, #f1f5f9);--_surface-active: var(--surface-active, #eef2ff);--_surface-muted: var(--surface-section, #f8fafc);--_border: var(--surface-border, #e2e8f0);--_text: var(--text-color, #0f172a);--_text-subtle: var(--text-color-secondary, #64748b);--_primary: var(--primary-color);--_primary-contrast: var(--primary-color-text, #fff);--_danger: var(--red-500, #ef4444);--_radius: 8px;display:block;inline-size:100%;font-size:13px;color:var(--_text)}:host(.fly-cron-builder--disabled){opacity:.6}.fly-cron__row{display:flex;align-items:center;gap:8px;margin-block-end:10px}.fly-cron__row--wrap{flex-wrap:wrap}.fly-cron__label{flex:0 0 auto;color:var(--_text-subtle);font-weight:600}.fly-cron__suffix{flex:0 0 auto;color:var(--_text-subtle)}.fly-cron__select,.fly-cron__num,.fly-cron__time{block-size:2.25rem;padding-inline:10px;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);color:var(--_text);font:inherit}.fly-cron__select{flex:1 1 auto;min-inline-size:10rem;cursor:pointer}.fly-cron__num{inline-size:4.5rem;text-align:end}.fly-cron__time{inline-size:auto}.fly-cron__select:focus-visible,.fly-cron__num:focus-visible,.fly-cron__time:focus-visible,.fly-cron__field-input:focus-visible{outline:2px solid var(--_primary);outline-offset:1px}.fly-cron__select:disabled,.fly-cron__num:disabled,.fly-cron__time:disabled,.fly-cron__field-input:disabled{opacity:.55;cursor:not-allowed}.fly-cron__weekdays{display:flex;flex-wrap:wrap;gap:6px}.fly-cron__chip{min-inline-size:2.5rem;block-size:2rem;padding-inline:10px;border:1px solid var(--_border);border-radius:999px;background:var(--_surface);color:var(--_text);font:inherit;cursor:pointer;transition:background .12s ease,border-color .12s ease,color .12s ease}.fly-cron__chip:hover:not(:disabled){background:var(--_surface-hover)}.fly-cron__chip--on{background:var(--_primary);border-color:var(--_primary);color:var(--_primary-contrast);font-weight:600}.fly-cron__chip:focus-visible{outline:2px solid var(--_primary);outline-offset:2px}.fly-cron__chip:disabled{cursor:not-allowed}.fly-cron__advanced{display:flex;flex-wrap:wrap;gap:8px;margin-block-end:6px}.fly-cron__field{display:flex;flex-direction:column;gap:4px}.fly-cron__field-label{color:var(--_text-subtle);font-size:11px;font-weight:600}.fly-cron__field-input{inline-size:5rem;block-size:2.25rem;padding-inline:8px;border:1px solid var(--_border);border-radius:6px;background:var(--_surface);color:var(--_text);font:inherit;text-align:center}.fly-cron__field-input[aria-invalid=true]{border-color:var(--_danger)}.fly-cron__hint{margin:0 0 8px;color:var(--_text-subtle);font-size:11px}.fly-cron__summary{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:8px 10px;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface-muted)}.fly-cron__summary--error{border-color:var(--_danger)}.fly-cron__summary-text{flex:1 1 auto}.fly-cron__expr{flex:0 0 auto;padding:2px 8px;border-radius:6px;background:var(--_surface);border:1px solid var(--_border);color:var(--_text-subtle);font-size:12px;direction:ltr;unicode-bidi:isolate}.fly-cron__mono{font-family:var(--font-family-mono, ui-monospace, \"SFMono-Regular\", \"Menlo\", \"Consolas\", monospace)}.fly-cron__error{margin:6px 0 0;color:var(--_danger);font-size:12px}\n"] }]
|
|
12423
12477
|
}], propDecorators: { seconds: [{ type: i0.Input, args: [{ isSignal: true, alias: "seconds", required: false }] }], allowNone: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowNone", required: false }] }], modes: [{ type: i0.Input, args: [{ isSignal: true, alias: "modes", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], firstDayOfWeek: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstDayOfWeek", required: false }] }], cronChange: [{ type: i0.Output, args: ["cronChange"] }], validityChange: [{ type: i0.Output, args: ["validityChange"] }], modeSelectRef: [{ type: i0.ViewChild, args: ['modeSelect', { isSignal: true }] }] } });
|
|
12424
12478
|
|
|
12479
|
+
/**
|
|
12480
|
+
* Public data contract for {@link FlyGanttComponent}.
|
|
12481
|
+
*
|
|
12482
|
+
* The types here are deliberately **app-agnostic** — no Projects/Tasks vocabulary leaks in.
|
|
12483
|
+
* A consumer maps its own domain rows to {@link GanttRow}[] + {@link GanttDependency}[],
|
|
12484
|
+
* feeds them in, and reacts to the emitted change events by persisting itself. The Gantt
|
|
12485
|
+
* never calls a backend and never mutates its inputs.
|
|
12486
|
+
*/
|
|
12487
|
+
/** Immutable list of the supported zoom levels, coarsest-last. */
|
|
12488
|
+
const GANTT_ZOOMS = ['day', 'week', 'month', 'quarter'];
|
|
12489
|
+
/** Immutable list of the supported relationship kinds, `FS` first. */
|
|
12490
|
+
const GANTT_DEPENDENCY_TYPES = [
|
|
12491
|
+
'FS',
|
|
12492
|
+
'SS',
|
|
12493
|
+
'FF',
|
|
12494
|
+
'SF',
|
|
12495
|
+
];
|
|
12496
|
+
/**
|
|
12497
|
+
* Alpha the chart paints {@link GanttRow.backgroundColor} at. Deliberately low and fixed: the
|
|
12498
|
+
* tint is a *band behind* the bars and the row label, so a consumer can pass a saturated brand
|
|
12499
|
+
* colour without pre-computing a pastel that survives both themes.
|
|
12500
|
+
*/
|
|
12501
|
+
const GANTT_ROW_TINT_ALPHA = 0.16;
|
|
12502
|
+
|
|
12425
12503
|
const MS_PER_DAY = 86_400_000;
|
|
12426
12504
|
/** Pixels allotted to one calendar day at each zoom level. Everything scales off this. */
|
|
12427
12505
|
const ZOOM_PX_PER_DAY = {
|
|
@@ -12677,11 +12755,16 @@ function resolveLinks(deps, renderableIds) {
|
|
|
12677
12755
|
continue;
|
|
12678
12756
|
if (!renderableIds.has(d.fromId) || !renderableIds.has(d.toId))
|
|
12679
12757
|
continue;
|
|
12680
|
-
|
|
12758
|
+
// `\u0000` (written as an escape, not as a literal NUL — a raw NUL byte in the source makes
|
|
12759
|
+
// the whole file read as binary to grep/ripgrep and silently excludes it from code search)
|
|
12760
|
+
// cannot occur in a row id, so it is a collision-proof pair separator.
|
|
12761
|
+
const key = `${d.fromId}\u0000${d.toId}`;
|
|
12762
|
+
// One relationship per ordered pair, first declaration wins: MS Project models at most one
|
|
12763
|
+
// link between two tasks, so a second edge for the same pair is a duplicate, not an SS+FF.
|
|
12681
12764
|
if (seen.has(key))
|
|
12682
12765
|
continue;
|
|
12683
12766
|
seen.add(key);
|
|
12684
|
-
out.push({ fromId: d.fromId, toId: d.toId });
|
|
12767
|
+
out.push({ fromId: d.fromId, toId: d.toId, type: d.type ?? 'FS' });
|
|
12685
12768
|
}
|
|
12686
12769
|
return out;
|
|
12687
12770
|
}
|
|
@@ -12795,15 +12878,37 @@ const HEADER_H = HEADER_UPPER_H + HEADER_LOWER_H;
|
|
|
12795
12878
|
const BAR_V_PAD = 6;
|
|
12796
12879
|
/** Edge-handle hit width (px) for resize + the link connector radius. */
|
|
12797
12880
|
const HANDLE_W = 8;
|
|
12881
|
+
/** Label-pane width bounds (px) the splitter clamps to. */
|
|
12882
|
+
const MIN_LABEL_W = 140;
|
|
12883
|
+
const MAX_LABEL_W = 720;
|
|
12884
|
+
/** Keyboard resize steps (plain arrow / with Shift). */
|
|
12885
|
+
const LABEL_STEP = 16;
|
|
12886
|
+
const LABEL_STEP_COARSE = 64;
|
|
12887
|
+
/** Straight run (px) a dependency elbow travels out of a bar edge before it turns. */
|
|
12888
|
+
const LINK_STUB = 12;
|
|
12889
|
+
/** Clamp any candidate label-pane width into the splitter's range. */
|
|
12890
|
+
function clampLabelWidth(px) {
|
|
12891
|
+
return Math.max(MIN_LABEL_W, Math.min(MAX_LABEL_W, Math.round(px)));
|
|
12892
|
+
}
|
|
12893
|
+
/**
|
|
12894
|
+
* Relationship kind implied by the edge a gesture left and the edge it landed on. The name is
|
|
12895
|
+
* literally `<predecessor endpoint><successor endpoint>`, so this is a lookup, not a rule.
|
|
12896
|
+
*/
|
|
12897
|
+
function linkTypeFor(from, to) {
|
|
12898
|
+
if (from === 'finish')
|
|
12899
|
+
return to === 'start' ? 'FS' : 'FF';
|
|
12900
|
+
return to === 'start' ? 'SS' : 'SF';
|
|
12901
|
+
}
|
|
12798
12902
|
/**
|
|
12799
12903
|
* **`fly-gantt`** — the design-system SVG Gantt chart.
|
|
12800
12904
|
*
|
|
12801
12905
|
* A reusable, **data-contract-driven** timeline: feed it {@link GanttRow}[] + optional
|
|
12802
12906
|
* {@link GanttDependency}[] and it renders a two-pane chart — an indented, collapsible label
|
|
12803
12907
|
* tree on the inline-start side and a scrolling time grid (header ticks, weekend shading, a
|
|
12804
|
-
* today line, task bars with progress fill, milestone diamonds
|
|
12805
|
-
* arrows) on the other.
|
|
12806
|
-
*
|
|
12908
|
+
* today line, task bars with progress fill, milestone diamonds, optional per-row colour bands
|
|
12909
|
+
* and MS-Project-style dependency arrows) on the other. The pane divider is draggable, pointer
|
|
12910
|
+
* drag moves/resizes bars and draws new dependencies, and the keyboard moves the selection,
|
|
12911
|
+
* nudges dates and removes the selected link. **It never persists** — every mutation is an
|
|
12807
12912
|
* output event the consumer acts on.
|
|
12808
12913
|
*
|
|
12809
12914
|
* Rendered on plain inline SVG (no third-party charting lib, no CDN asset). All geometry is
|
|
@@ -12812,8 +12917,10 @@ const HANDLE_W = 8;
|
|
|
12812
12917
|
*
|
|
12813
12918
|
* Self-sufficient i18n: every user-visible string comes from the `gantt.*` keys in
|
|
12814
12919
|
* `DS_BASELINE_LOCALES` (en/ar/fr/ur), overridable by any consumer key of the same name.
|
|
12815
|
-
* Styling
|
|
12816
|
-
*
|
|
12920
|
+
* Styling reads the **app-content** token family (`--bg-2`, `--bg-3`, `--line-3`, `--ink`,
|
|
12921
|
+
* `--accent`, …) that dresses `fly-data-table` and the rest of the app-surface kit, so a Gantt
|
|
12922
|
+
* placed among those controls matches them; the shell-chrome family (`--surface-card`, …) and
|
|
12923
|
+
* then a light-neutral literal are chained as fallbacks for consumers that map neither.
|
|
12817
12924
|
*
|
|
12818
12925
|
* **Scale limit:** renders up to `maxRows` (default 500) visible rows; beyond that the list is
|
|
12819
12926
|
* capped and a footer notes the overflow. Heavy windowing/virtualization is intentionally
|
|
@@ -12830,8 +12937,14 @@ class FlyGanttComponent {
|
|
|
12830
12937
|
showToday = input(true, ...(ngDevMode ? [{ debugName: "showToday" }] : /* istanbul ignore next */ []));
|
|
12831
12938
|
/** Globally disable drag/resize/link (individual rows can also be `readonly`). */
|
|
12832
12939
|
readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
|
|
12833
|
-
/**
|
|
12940
|
+
/**
|
|
12941
|
+
* Inline-start label-pane width in px — the **initial** width. The user can drag the pane
|
|
12942
|
+
* divider from there; once they have, this input only re-seeds the pane if
|
|
12943
|
+
* {@link resetLabelWidth} is called (double-clicking the divider does exactly that).
|
|
12944
|
+
*/
|
|
12834
12945
|
labelWidth = input(240, ...(ngDevMode ? [{ debugName: "labelWidth" }] : /* istanbul ignore next */ []));
|
|
12946
|
+
/** Let the user drag the divider between the label pane and the time grid. */
|
|
12947
|
+
resizableLabels = input(true, ...(ngDevMode ? [{ debugName: "resizableLabels" }] : /* istanbul ignore next */ []));
|
|
12835
12948
|
/** Row band height in px. */
|
|
12836
12949
|
rowHeight = input(34, ...(ngDevMode ? [{ debugName: "rowHeight" }] : /* istanbul ignore next */ []));
|
|
12837
12950
|
/** Hard cap on rendered rows; beyond it the list is truncated (see class doc). */
|
|
@@ -12839,10 +12952,17 @@ class FlyGanttComponent {
|
|
|
12839
12952
|
// ── Outputs ────────────────────────────────────────────────────────────────
|
|
12840
12953
|
/** Fires on a committed bar move / resize with the new ISO `start`/`end`. */
|
|
12841
12954
|
rowDatesChange = output();
|
|
12842
|
-
/**
|
|
12955
|
+
/**
|
|
12956
|
+
* Fires when the user drags a link between two rows. `type` is derived from the edge the drag
|
|
12957
|
+
* started on and the edge of the target row it was dropped nearest to.
|
|
12958
|
+
*/
|
|
12843
12959
|
dependencyCreate = output();
|
|
12960
|
+
/** Fires when the user removes the selected dependency arrow (badge click or Delete). */
|
|
12961
|
+
dependencyDelete = output();
|
|
12844
12962
|
rowClick = output();
|
|
12845
12963
|
rowDblClick = output();
|
|
12964
|
+
/** Fires once per committed divider drag / keyboard resize with the new pane width in px. */
|
|
12965
|
+
labelWidthChange = output();
|
|
12846
12966
|
// ── Identity + geometry constants (template-visible) ─────────────────────────
|
|
12847
12967
|
_uid = ++_flyGanttUid;
|
|
12848
12968
|
markerId = `fly-gantt-${this._uid}-arrow`;
|
|
@@ -12850,20 +12970,30 @@ class FlyGanttComponent {
|
|
|
12850
12970
|
HEADER_UPPER_H = HEADER_UPPER_H;
|
|
12851
12971
|
HEADER_LOWER_H = HEADER_LOWER_H;
|
|
12852
12972
|
HANDLE_W = HANDLE_W;
|
|
12973
|
+
MIN_LABEL_W = MIN_LABEL_W;
|
|
12974
|
+
MAX_LABEL_W = MAX_LABEL_W;
|
|
12853
12975
|
// ── Interaction state ─────────────────────────────────────────────────────────
|
|
12854
12976
|
selectedId = signal(null, ...(ngDevMode ? [{ debugName: "selectedId" }] : /* istanbul ignore next */ []));
|
|
12977
|
+
/** Key (`from~to~type`) of the dependency arrow the user has selected, if any. */
|
|
12978
|
+
selectedLinkKey = signal(null, ...(ngDevMode ? [{ debugName: "selectedLinkKey" }] : /* istanbul ignore next */ []));
|
|
12855
12979
|
_collapsed = signal(new Set(), ...(ngDevMode ? [{ debugName: "_collapsed" }] : /* istanbul ignore next */ []));
|
|
12856
12980
|
/** Live drag preview `{id,start,end}` folded into geometry while a gesture runs. */
|
|
12857
12981
|
_dragPreview = signal(null, ...(ngDevMode ? [{ debugName: "_dragPreview" }] : /* istanbul ignore next */ []));
|
|
12858
|
-
/** Rubber-band link gesture render-space anchor + cursor, and its origin row. */
|
|
12982
|
+
/** Rubber-band link gesture render-space anchor + cursor, and its origin row + edge. */
|
|
12859
12983
|
linkGesture = signal(null, ...(ngDevMode ? [{ debugName: "linkGesture" }] : /* istanbul ignore next */ []));
|
|
12860
12984
|
dragTooltip = signal(null, ...(ngDevMode ? [{ debugName: "dragTooltip" }] : /* istanbul ignore next */ []));
|
|
12985
|
+
/** True while a divider drag is in flight (suppresses text selection host-wide). */
|
|
12986
|
+
resizingLabels = signal(false, ...(ngDevMode ? [{ debugName: "resizingLabels" }] : /* istanbul ignore next */ []));
|
|
12987
|
+
/** User-chosen pane width; `null` = still following the {@link labelWidth} input. */
|
|
12988
|
+
_labelWidthOverride = signal(null, ...(ngDevMode ? [{ debugName: "_labelWidthOverride" }] : /* istanbul ignore next */ []));
|
|
12861
12989
|
bodyRef = viewChild('bodySvg', ...(ngDevMode ? [{ debugName: "bodyRef" }] : /* istanbul ignore next */ []));
|
|
12862
12990
|
// ── Derived model ──────────────────────────────────────────────────────────────
|
|
12863
12991
|
rtl = computed(() => this.i18n.isRtl(), ...(ngDevMode ? [{ debugName: "rtl" }] : /* istanbul ignore next */ []));
|
|
12864
12992
|
pxPerDay = computed(() => ZOOM_PX_PER_DAY[this.zoom()], ...(ngDevMode ? [{ debugName: "pxPerDay" }] : /* istanbul ignore next */ []));
|
|
12865
12993
|
domain = computed(() => computeDomain(this.rows(), this.zoom()), ...(ngDevMode ? [{ debugName: "domain" }] : /* istanbul ignore next */ []));
|
|
12866
12994
|
innerWidth = computed(() => Math.max(1, dateToX(this.domain().end, this.domain().start, this.pxPerDay())), ...(ngDevMode ? [{ debugName: "innerWidth" }] : /* istanbul ignore next */ []));
|
|
12995
|
+
/** Label-pane width actually rendered — the user's dragged width, else the input. */
|
|
12996
|
+
effectiveLabelWidth = computed(() => clampLabelWidth(this._labelWidthOverride() ?? this.labelWidth()), ...(ngDevMode ? [{ debugName: "effectiveLabelWidth" }] : /* istanbul ignore next */ []));
|
|
12867
12997
|
/** Full visible list after tree flatten/collapse. */
|
|
12868
12998
|
_flat = computed(() => flattenRows(this.rows(), this._collapsed()), ...(ngDevMode ? [{ debugName: "_flat" }] : /* istanbul ignore next */ []));
|
|
12869
12999
|
/** Capped list actually rendered. */
|
|
@@ -12909,6 +13039,7 @@ class FlyGanttComponent {
|
|
|
12909
13039
|
}
|
|
12910
13040
|
const editable = !globalReadonly && !row.readonly && !isGroup;
|
|
12911
13041
|
const color = row.color ?? null;
|
|
13042
|
+
const tint = this._tintFor(row);
|
|
12912
13043
|
if (isMilestone) {
|
|
12913
13044
|
const at = start ?? end;
|
|
12914
13045
|
const logical = at ? dateToX(at, dom.start, ppd) : 0;
|
|
@@ -12916,7 +13047,7 @@ class FlyGanttComponent {
|
|
|
12916
13047
|
return {
|
|
12917
13048
|
flat, index, y, midY, shape: at ? 'milestone' : 'empty',
|
|
12918
13049
|
x: pointX, width: 0, progressWidth: 0, pointX,
|
|
12919
|
-
startX: pointX, endX: pointX, color,
|
|
13050
|
+
startX: pointX, endX: pointX, color, tint,
|
|
12920
13051
|
ariaLabel: this._ariaFor(row, start, end, 'milestone'),
|
|
12921
13052
|
editable: editable && !!at,
|
|
12922
13053
|
};
|
|
@@ -12924,7 +13055,7 @@ class FlyGanttComponent {
|
|
|
12924
13055
|
if (!start || !end) {
|
|
12925
13056
|
return {
|
|
12926
13057
|
flat, index, y, midY, shape: 'empty',
|
|
12927
|
-
x: 0, width: 0, progressWidth: 0, pointX: 0, startX: 0, endX: 0, color,
|
|
13058
|
+
x: 0, width: 0, progressWidth: 0, pointX: 0, startX: 0, endX: 0, color, tint,
|
|
12928
13059
|
ariaLabel: this._ariaFor(row, start, end, isGroup ? 'group' : 'bar'),
|
|
12929
13060
|
editable: false,
|
|
12930
13061
|
};
|
|
@@ -12942,12 +13073,14 @@ class FlyGanttComponent {
|
|
|
12942
13073
|
pointX: 0,
|
|
12943
13074
|
startX: mapX(xs, w, rtl),
|
|
12944
13075
|
endX: mapX(xe, w, rtl),
|
|
12945
|
-
color,
|
|
13076
|
+
color, tint,
|
|
12946
13077
|
ariaLabel: this._ariaFor(row, start, end, isGroup ? 'group' : 'bar'),
|
|
12947
13078
|
editable: isGroup ? false : editable,
|
|
12948
13079
|
};
|
|
12949
13080
|
});
|
|
12950
13081
|
}, ...(ngDevMode ? [{ debugName: "rowVms" }] : /* istanbul ignore next */ []));
|
|
13082
|
+
/** Only the rows carrying a tint, so the band pass does not emit an empty rect per row. */
|
|
13083
|
+
tintedRows = computed(() => this.rowVms().filter((vm) => vm.tint !== null), ...(ngDevMode ? [{ debugName: "tintedRows" }] : /* istanbul ignore next */ []));
|
|
12951
13084
|
/** Dependency arrows between currently-rendered rows (missing/collapsed endpoints skipped). */
|
|
12952
13085
|
linkVms = computed(() => {
|
|
12953
13086
|
const vmById = new Map();
|
|
@@ -12962,7 +13095,21 @@ class FlyGanttComponent {
|
|
|
12962
13095
|
const to = vmById.get(link.toId);
|
|
12963
13096
|
if (!from || !to)
|
|
12964
13097
|
continue;
|
|
12965
|
-
|
|
13098
|
+
const routed = this._route(from, to, link.type, rtl);
|
|
13099
|
+
out.push({
|
|
13100
|
+
key: `${link.fromId}~${link.toId}~${link.type}`,
|
|
13101
|
+
fromId: link.fromId,
|
|
13102
|
+
toId: link.toId,
|
|
13103
|
+
type: link.type,
|
|
13104
|
+
path: routed.path,
|
|
13105
|
+
badgeX: routed.badgeX,
|
|
13106
|
+
badgeY: routed.badgeY,
|
|
13107
|
+
ariaLabel: this.i18n.t('gantt.aria.link', {
|
|
13108
|
+
type: this.i18n.t(`gantt.link_type.${link.type.toLowerCase()}`),
|
|
13109
|
+
from: from.flat.row.label,
|
|
13110
|
+
to: to.flat.row.label,
|
|
13111
|
+
}),
|
|
13112
|
+
});
|
|
12966
13113
|
}
|
|
12967
13114
|
return out;
|
|
12968
13115
|
}, ...(ngDevMode ? [{ debugName: "linkVms" }] : /* istanbul ignore next */ []));
|
|
@@ -12980,6 +13127,13 @@ class FlyGanttComponent {
|
|
|
12980
13127
|
const { pointX: cx, midY: cy } = vm;
|
|
12981
13128
|
return `${cx},${cy - r} ${cx + r},${cy} ${cx},${cy + r} ${cx - r},${cy}`;
|
|
12982
13129
|
}
|
|
13130
|
+
/**
|
|
13131
|
+
* Link connector x for a milestone. A diamond has no width, so the connector is nudged clear
|
|
13132
|
+
* of the marker in the forward-in-time direction (which flips under RTL).
|
|
13133
|
+
*/
|
|
13134
|
+
milestoneConnectorX(vm) {
|
|
13135
|
+
return vm.pointX + (this.milestoneR() + 5) * (this.rtl() ? -1 : 1);
|
|
13136
|
+
}
|
|
12983
13137
|
/** Summary-bracket path (thin bar + down-turned end caps) for a group row. */
|
|
12984
13138
|
groupPath(vm) {
|
|
12985
13139
|
const top = this.barTop(vm);
|
|
@@ -12995,6 +13149,88 @@ class FlyGanttComponent {
|
|
|
12995
13149
|
indentFor(depth) {
|
|
12996
13150
|
return 8 + depth * 16;
|
|
12997
13151
|
}
|
|
13152
|
+
// ── Label-pane divider ─────────────────────────────────────────────────────────
|
|
13153
|
+
/**
|
|
13154
|
+
* Pointer-drag the divider. Forward-in-time is `+x` LTR and `-x` RTL, and the label pane sits
|
|
13155
|
+
* on the inline-start side in both, so the same sign flip that mirrors the timeline also
|
|
13156
|
+
* mirrors "drag outward = wider".
|
|
13157
|
+
*/
|
|
13158
|
+
onLabelResizePointerDown(ev) {
|
|
13159
|
+
if (!this.resizableLabels() || ev.button !== 0)
|
|
13160
|
+
return;
|
|
13161
|
+
ev.preventDefault();
|
|
13162
|
+
ev.stopPropagation();
|
|
13163
|
+
const startClientX = ev.clientX;
|
|
13164
|
+
const startWidth = this.effectiveLabelWidth();
|
|
13165
|
+
const dirSign = this.rtl() ? -1 : 1;
|
|
13166
|
+
this.resizingLabels.set(true);
|
|
13167
|
+
const onMove = (e) => {
|
|
13168
|
+
this._labelWidthOverride.set(clampLabelWidth(startWidth + (e.clientX - startClientX) * dirSign));
|
|
13169
|
+
};
|
|
13170
|
+
const onUp = () => {
|
|
13171
|
+
document.removeEventListener('pointermove', onMove);
|
|
13172
|
+
document.removeEventListener('pointerup', onUp);
|
|
13173
|
+
this.resizingLabels.set(false);
|
|
13174
|
+
const final = this.effectiveLabelWidth();
|
|
13175
|
+
if (final !== startWidth)
|
|
13176
|
+
this.labelWidthChange.emit(final);
|
|
13177
|
+
};
|
|
13178
|
+
document.addEventListener('pointermove', onMove);
|
|
13179
|
+
document.addEventListener('pointerup', onUp);
|
|
13180
|
+
this.destroyRef.onDestroy(() => {
|
|
13181
|
+
document.removeEventListener('pointermove', onMove);
|
|
13182
|
+
document.removeEventListener('pointerup', onUp);
|
|
13183
|
+
});
|
|
13184
|
+
}
|
|
13185
|
+
/**
|
|
13186
|
+
* Keyboard resize on the focused divider. ArrowRight/Left grow/shrink by the *inline* meaning
|
|
13187
|
+
* of the key, so under RTL the arrow that points away from the pane is still the one that
|
|
13188
|
+
* widens it. `Home`/`End` jump to the bounds, Enter/Escape restore the input width.
|
|
13189
|
+
*
|
|
13190
|
+
* Typed `Event` for the same reason as {@link onLabelRowKeydown}: Angular's strict template
|
|
13191
|
+
* checker types `(keydown)` `$event` as `Event`, so the cast is done here once.
|
|
13192
|
+
*/
|
|
13193
|
+
onLabelResizeKeydown(ev) {
|
|
13194
|
+
if (!this.resizableLabels())
|
|
13195
|
+
return;
|
|
13196
|
+
const key = ev.key;
|
|
13197
|
+
const coarse = ev.shiftKey;
|
|
13198
|
+
const step = coarse ? LABEL_STEP_COARSE : LABEL_STEP;
|
|
13199
|
+
const grow = this.rtl() ? 'ArrowLeft' : 'ArrowRight';
|
|
13200
|
+
const shrink = this.rtl() ? 'ArrowRight' : 'ArrowLeft';
|
|
13201
|
+
let next;
|
|
13202
|
+
if (key === grow)
|
|
13203
|
+
next = this.effectiveLabelWidth() + step;
|
|
13204
|
+
else if (key === shrink)
|
|
13205
|
+
next = this.effectiveLabelWidth() - step;
|
|
13206
|
+
else if (key === 'Home')
|
|
13207
|
+
next = MIN_LABEL_W;
|
|
13208
|
+
else if (key === 'End')
|
|
13209
|
+
next = MAX_LABEL_W;
|
|
13210
|
+
else if (key === 'Enter' || key === 'Escape') {
|
|
13211
|
+
// Stop the grid-level Enter handler from also activating the selected row.
|
|
13212
|
+
ev.preventDefault();
|
|
13213
|
+
ev.stopPropagation();
|
|
13214
|
+
this.resetLabelWidth();
|
|
13215
|
+
return;
|
|
13216
|
+
}
|
|
13217
|
+
else
|
|
13218
|
+
return;
|
|
13219
|
+
ev.preventDefault();
|
|
13220
|
+
ev.stopPropagation();
|
|
13221
|
+
const clamped = clampLabelWidth(next);
|
|
13222
|
+
if (clamped === this.effectiveLabelWidth())
|
|
13223
|
+
return;
|
|
13224
|
+
this._labelWidthOverride.set(clamped);
|
|
13225
|
+
this.labelWidthChange.emit(clamped);
|
|
13226
|
+
}
|
|
13227
|
+
/** Drop the user's dragged width and fall back to the {@link labelWidth} input. */
|
|
13228
|
+
resetLabelWidth() {
|
|
13229
|
+
if (this._labelWidthOverride() === null)
|
|
13230
|
+
return;
|
|
13231
|
+
this._labelWidthOverride.set(null);
|
|
13232
|
+
this.labelWidthChange.emit(this.effectiveLabelWidth());
|
|
13233
|
+
}
|
|
12998
13234
|
// ── Group collapse ─────────────────────────────────────────────────────────────
|
|
12999
13235
|
isCollapsed(id) {
|
|
13000
13236
|
return this._collapsed().has(id);
|
|
@@ -13022,13 +13258,39 @@ class FlyGanttComponent {
|
|
|
13022
13258
|
// ── Row activation ─────────────────────────────────────────────────────────────
|
|
13023
13259
|
onRowClick(id) {
|
|
13024
13260
|
this.selectedId.set(id);
|
|
13261
|
+
// A row and a link are alternative selections — picking one drops the other, so Delete is
|
|
13262
|
+
// never ambiguous about which thing it would remove.
|
|
13263
|
+
this.selectedLinkKey.set(null);
|
|
13025
13264
|
this.rowClick.emit(id);
|
|
13026
13265
|
}
|
|
13027
13266
|
onRowDblClick(id) {
|
|
13028
13267
|
this.rowDblClick.emit(id);
|
|
13029
13268
|
}
|
|
13269
|
+
// ── Dependency arrow selection + removal ───────────────────────────────────────
|
|
13270
|
+
onLinkClick(ev, vm) {
|
|
13271
|
+
ev.stopPropagation();
|
|
13272
|
+
this.selectedLinkKey.set(this.selectedLinkKey() === vm.key ? null : vm.key);
|
|
13273
|
+
}
|
|
13274
|
+
deleteLink(ev, vm) {
|
|
13275
|
+
ev.stopPropagation();
|
|
13276
|
+
ev.preventDefault();
|
|
13277
|
+
if (this.readonly())
|
|
13278
|
+
return;
|
|
13279
|
+
this.selectedLinkKey.set(null);
|
|
13280
|
+
this.dependencyDelete.emit({ fromId: vm.fromId, toId: vm.toId, type: vm.type });
|
|
13281
|
+
}
|
|
13030
13282
|
// ── Keyboard ───────────────────────────────────────────────────────────────────
|
|
13031
13283
|
onGridKeydown(ev) {
|
|
13284
|
+
// Link removal is checked before the row branches: while an arrow is selected the grid's
|
|
13285
|
+
// Delete belongs to it, and selecting an arrow already cleared the row selection.
|
|
13286
|
+
if (ev.key === 'Delete' || ev.key === 'Backspace') {
|
|
13287
|
+
const selected = this.linkVms().find((l) => l.key === this.selectedLinkKey());
|
|
13288
|
+
if (selected) {
|
|
13289
|
+
ev.preventDefault();
|
|
13290
|
+
this.deleteLink(ev, selected);
|
|
13291
|
+
}
|
|
13292
|
+
return;
|
|
13293
|
+
}
|
|
13032
13294
|
const list = this.visibleRows();
|
|
13033
13295
|
if (list.length === 0)
|
|
13034
13296
|
return;
|
|
@@ -13095,6 +13357,7 @@ class FlyGanttComponent {
|
|
|
13095
13357
|
ev.preventDefault();
|
|
13096
13358
|
ev.stopPropagation();
|
|
13097
13359
|
this.selectedId.set(vm.flat.row.id);
|
|
13360
|
+
this.selectedLinkKey.set(null);
|
|
13098
13361
|
const dates = this._resolvedDates().get(vm.flat.row.id);
|
|
13099
13362
|
if (!dates?.start || !dates?.end)
|
|
13100
13363
|
return;
|
|
@@ -13149,13 +13412,19 @@ class FlyGanttComponent {
|
|
|
13149
13412
|
});
|
|
13150
13413
|
}
|
|
13151
13414
|
// ── Pointer drag: dependency create ──────────────────────────────────────────────
|
|
13152
|
-
|
|
13415
|
+
/**
|
|
13416
|
+
* Start a link gesture from one end of a bar. Which end it started on, plus which end of the
|
|
13417
|
+
* target row it is dropped nearest to, is what selects the {@link GanttDependencyType} — so all
|
|
13418
|
+
* four MS-Project relationships are reachable with the same single drag.
|
|
13419
|
+
*/
|
|
13420
|
+
onLinkPointerDown(ev, vm, anchor) {
|
|
13153
13421
|
if (this.readonly() || vm.flat.row.readonly || ev.button !== 0)
|
|
13154
13422
|
return;
|
|
13155
13423
|
ev.preventDefault();
|
|
13156
13424
|
ev.stopPropagation();
|
|
13157
13425
|
const local = this._toBodyPoint(ev);
|
|
13158
|
-
|
|
13426
|
+
const x0 = vm.shape === 'milestone' ? vm.pointX : anchor === 'finish' ? vm.endX : vm.startX;
|
|
13427
|
+
this.linkGesture.set({ fromId: vm.flat.row.id, anchor, x0, y0: vm.midY, x1: local.x, y1: local.y });
|
|
13159
13428
|
const onMove = (e) => {
|
|
13160
13429
|
const p = this._toBodyPoint(e);
|
|
13161
13430
|
const g = this.linkGesture();
|
|
@@ -13169,9 +13438,13 @@ class FlyGanttComponent {
|
|
|
13169
13438
|
this.linkGesture.set(null);
|
|
13170
13439
|
if (!g)
|
|
13171
13440
|
return;
|
|
13172
|
-
const
|
|
13173
|
-
if (
|
|
13174
|
-
this.dependencyCreate.emit({
|
|
13441
|
+
const target = this._dropTargetAt(e.clientX, e.clientY);
|
|
13442
|
+
if (target && target.id !== g.fromId) {
|
|
13443
|
+
this.dependencyCreate.emit({
|
|
13444
|
+
fromId: g.fromId,
|
|
13445
|
+
toId: target.id,
|
|
13446
|
+
type: linkTypeFor(g.anchor, target.anchor),
|
|
13447
|
+
});
|
|
13175
13448
|
}
|
|
13176
13449
|
};
|
|
13177
13450
|
document.addEventListener('pointermove', onMove);
|
|
@@ -13189,6 +13462,17 @@ class FlyGanttComponent {
|
|
|
13189
13462
|
return `M ${g.x0} ${g.y0} L ${g.x1} ${g.y1}`;
|
|
13190
13463
|
}, ...(ngDevMode ? [{ debugName: "linkGesturePath" }] : /* istanbul ignore next */ []));
|
|
13191
13464
|
// ── Internals ────────────────────────────────────────────────────────────────────
|
|
13465
|
+
/**
|
|
13466
|
+
* Resolve a row's `backgroundColor` into the low-alpha band colour actually painted.
|
|
13467
|
+
* `color-mix` does the fade in the consumer's own colour space, so a token, a hex, an
|
|
13468
|
+
* `rgb()` or an `oklch()` all work and none of them need pre-computing per theme.
|
|
13469
|
+
*/
|
|
13470
|
+
_tintFor(row) {
|
|
13471
|
+
const raw = row.backgroundColor?.trim();
|
|
13472
|
+
if (!raw)
|
|
13473
|
+
return null;
|
|
13474
|
+
return `color-mix(in srgb, ${raw} ${Math.round(GANTT_ROW_TINT_ALPHA * 100)}%, transparent)`;
|
|
13475
|
+
}
|
|
13192
13476
|
_mapTicks(ticks) {
|
|
13193
13477
|
const w = this.innerWidth();
|
|
13194
13478
|
const rtl = this.rtl();
|
|
@@ -13197,16 +13481,41 @@ class FlyGanttComponent {
|
|
|
13197
13481
|
// Reflect each cell: its inline-start edge becomes innerWidth - (x + width).
|
|
13198
13482
|
return ticks.map((t) => ({ ...t, x: w - t.x - t.width }));
|
|
13199
13483
|
}
|
|
13200
|
-
/**
|
|
13201
|
-
|
|
13484
|
+
/**
|
|
13485
|
+
* Route a dependency elbow for any of the four relationship kinds (render space).
|
|
13486
|
+
*
|
|
13487
|
+
* The kind names the two endpoints, so it decides both the anchor x's and the direction the
|
|
13488
|
+
* arrow travels: leaving a *finish* edge moves forward in time, leaving a *start* edge moves
|
|
13489
|
+
* backward; entering a *start* edge approaches from behind, entering a *finish* edge from
|
|
13490
|
+
* ahead. `fwd` folds RTL in, so none of that branches on direction.
|
|
13491
|
+
*
|
|
13492
|
+
* One turn suffices when the entry stub lies ahead of the exit stub in the approach
|
|
13493
|
+
* direction. When it does not — overlapping or reversed bars, which SS/FF/SF hit constantly —
|
|
13494
|
+
* a straight drop would cut back through the bars, so the elbow detours via the gap between
|
|
13495
|
+
* the two rows instead.
|
|
13496
|
+
*/
|
|
13497
|
+
_route(from, to, type, rtl) {
|
|
13202
13498
|
const fwd = rtl ? -1 : 1;
|
|
13203
|
-
const
|
|
13204
|
-
const
|
|
13499
|
+
const fromFinish = type === 'FS' || type === 'FF';
|
|
13500
|
+
const toStart = type === 'FS' || type === 'SS';
|
|
13501
|
+
const x1 = from.shape === 'milestone' ? from.pointX : fromFinish ? from.endX : from.startX;
|
|
13502
|
+
const x2 = to.shape === 'milestone' ? to.pointX : toStart ? to.startX : to.endX;
|
|
13205
13503
|
const y1 = from.midY;
|
|
13206
|
-
const x2 = to.shape === 'milestone' ? to.pointX : to.startX;
|
|
13207
13504
|
const y2 = to.midY;
|
|
13208
|
-
|
|
13209
|
-
|
|
13505
|
+
const d1 = (fromFinish ? 1 : -1) * fwd; // exit direction
|
|
13506
|
+
const d2 = (toStart ? 1 : -1) * fwd; // approach direction
|
|
13507
|
+
const exit = x1 + LINK_STUB * d1;
|
|
13508
|
+
const entry = x2 - LINK_STUB * d2;
|
|
13509
|
+
if ((entry - exit) * d2 >= 0) {
|
|
13510
|
+
return { path: `M ${x1} ${y1} H ${exit} V ${y2} H ${x2}`, badgeX: exit, badgeY: (y1 + y2) / 2 };
|
|
13511
|
+
}
|
|
13512
|
+
// Distinct rows always differ in y; the guard only keeps a degenerate input off the bar.
|
|
13513
|
+
const yMid = y1 === y2 ? y1 - this.rowHeight() / 2 : (y1 + y2) / 2;
|
|
13514
|
+
return {
|
|
13515
|
+
path: `M ${x1} ${y1} H ${exit} V ${yMid} H ${entry} V ${y2} H ${x2}`,
|
|
13516
|
+
badgeX: entry,
|
|
13517
|
+
badgeY: yMid,
|
|
13518
|
+
};
|
|
13210
13519
|
}
|
|
13211
13520
|
/** Convert a pointer event to a coordinate inside the body SVG (render space). */
|
|
13212
13521
|
_toBodyPoint(ev) {
|
|
@@ -13216,11 +13525,25 @@ class FlyGanttComponent {
|
|
|
13216
13525
|
const rect = svg.getBoundingClientRect();
|
|
13217
13526
|
return { x: ev.clientX - rect.left, y: ev.clientY - rect.top };
|
|
13218
13527
|
}
|
|
13219
|
-
/**
|
|
13220
|
-
|
|
13528
|
+
/**
|
|
13529
|
+
* The row a link gesture was dropped on **and which of its ends** the drop landed nearest —
|
|
13530
|
+
* the second half of the type derivation. A milestone has no width, so both of its ends are
|
|
13531
|
+
* the same point and it is reported as `start`, giving the FS/SS pair anyone actually wants.
|
|
13532
|
+
*/
|
|
13533
|
+
_dropTargetAt(clientX, clientY) {
|
|
13221
13534
|
const el = document.elementFromPoint(clientX, clientY);
|
|
13222
13535
|
const holder = el?.closest('[data-row-id]');
|
|
13223
|
-
|
|
13536
|
+
const id = holder?.getAttribute('data-row-id');
|
|
13537
|
+
if (!id)
|
|
13538
|
+
return null;
|
|
13539
|
+
const vm = this.rowVms().find((v) => v.flat.row.id === id);
|
|
13540
|
+
if (!vm || vm.shape === 'milestone' || vm.shape === 'empty')
|
|
13541
|
+
return { id, anchor: 'start' };
|
|
13542
|
+
const x = this._toBodyPoint({ clientX, clientY }).x;
|
|
13543
|
+
return {
|
|
13544
|
+
id,
|
|
13545
|
+
anchor: Math.abs(x - vm.endX) < Math.abs(x - vm.startX) ? 'finish' : 'start',
|
|
13546
|
+
};
|
|
13224
13547
|
}
|
|
13225
13548
|
_ariaFor(row, start, end, shape) {
|
|
13226
13549
|
const t = (k, p) => this.i18n.t(k, p);
|
|
@@ -13241,7 +13564,7 @@ class FlyGanttComponent {
|
|
|
13241
13564
|
: t('gantt.aria.bar', { label: row.label, start: toIsoDate(start), end: toIsoDate(end), progress });
|
|
13242
13565
|
}
|
|
13243
13566
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyGanttComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13244
|
-
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 }, 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", rowClick: "rowClick", rowDblClick: "rowDblClick" }, host: { properties: { "class.fly-gantt--rtl": "rtl()", "class.fly-gantt--readonly": "readonly()", "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]=\"labelWidth() + '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\"></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 [style.height.px]=\"rowHeight()\"\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 <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 </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 <!-- Weekend shading + vertical gridlines (behind everything). -->\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 }\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 <!-- Finish connector: drag to draw a dependency to another row. -->\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)\"\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). -->\r\n @for (link of linkVms(); track link.key) {\r\n <path class=\"fly-gantt__link\" [attr.d]=\"link.path\" [attr.marker-end]=\"'url(#' + markerId + ')'\" />\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(--surface-card, #fff);--_surface-alt: var(--glass-bg-elevated, #f8fafc);--_surface-hover: var(--surface-hover, #f1f5f9);--_border: var(--surface-border, #e2e8f0);--_grid-line: var(--surface-border, #eef2f7);--_text: var(--text-color, #0f172a);--_text-subtle: var(--text-color-secondary, #64748b);--_primary: var(--primary-color);--_primary-contrast: var(--primary-color-text, #fff);--_bar: var(--primary-color);--_bar-progress: var(--primary-600, #4f46e5);--_weekend: rgb(100 116 139 / 9%);--_today: var(--red-500, #ef4444);--_link: var(--text-color-secondary, #64748b);--_radius: 8px;display:block;inline-size:100%;block-size:100%;min-block-size:200px;color:var(--_text);font-size:13px}.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);outline:none}.fly-gantt__scroll:focus-visible{outline:2px solid var(--_primary);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)}.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)}.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__labels{position:sticky;inset-inline-start:0;z-index:2;background:var(--_surface);border-inline-end:1px solid var(--_border)}.fly-gantt__label-row{display:flex;align-items:center;border-block-end:1px solid var(--_grid-line);cursor:pointer}.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(--_primary)}.fly-gantt__label-inner{display:flex;align-items:center;gap:4px;inline-size:100%;min-inline-size:0;padding-inline-end:8px}.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 .12s ease}.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(--_primary);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__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(--_primary);stroke-width:1.5;cursor:crosshair;opacity:0;transition:opacity .12s ease}.fly-gantt__row:hover .fly-gantt__connector{opacity:1}.fly-gantt__milestone{fill:var(--_primary);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--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-subtle);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 });
|
|
13245
13568
|
}
|
|
13246
13569
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyGanttComponent, decorators: [{
|
|
13247
13570
|
type: Component,
|
|
@@ -13249,20 +13572,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
|
|
|
13249
13572
|
class: 'fly-gantt',
|
|
13250
13573
|
'[class.fly-gantt--rtl]': 'rtl()',
|
|
13251
13574
|
'[class.fly-gantt--readonly]': 'readonly()',
|
|
13575
|
+
'[class.fly-gantt--resizing]': 'resizingLabels()',
|
|
13252
13576
|
'[attr.dir]': 'i18n.direction()',
|
|
13253
|
-
}, 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]=\"labelWidth() + '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\"></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 [style.height.px]=\"rowHeight()\"\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 <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 </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 <!-- Weekend shading + vertical gridlines (behind everything). -->\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 }\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 <!-- Finish connector: drag to draw a dependency to another row. -->\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)\"\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). -->\r\n @for (link of linkVms(); track link.key) {\r\n <path class=\"fly-gantt__link\" [attr.d]=\"link.path\" [attr.marker-end]=\"'url(#' + markerId + ')'\" />\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(--surface-card, #fff);--_surface-alt: var(--glass-bg-elevated, #f8fafc);--_surface-hover: var(--surface-hover, #f1f5f9);--_border: var(--surface-border, #e2e8f0);--_grid-line: var(--surface-border, #eef2f7);--_text: var(--text-color, #0f172a);--_text-subtle: var(--text-color-secondary, #64748b);--_primary: var(--primary-color);--_primary-contrast: var(--primary-color-text, #fff);--_bar: var(--primary-color);--_bar-progress: var(--primary-600, #4f46e5);--_weekend: rgb(100 116 139 / 9%);--_today: var(--red-500, #ef4444);--_link: var(--text-color-secondary, #64748b);--_radius: 8px;display:block;inline-size:100%;block-size:100%;min-block-size:200px;color:var(--_text);font-size:13px}.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);outline:none}.fly-gantt__scroll:focus-visible{outline:2px solid var(--_primary);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)}.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)}.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__labels{position:sticky;inset-inline-start:0;z-index:2;background:var(--_surface);border-inline-end:1px solid var(--_border)}.fly-gantt__label-row{display:flex;align-items:center;border-block-end:1px solid var(--_grid-line);cursor:pointer}.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(--_primary)}.fly-gantt__label-inner{display:flex;align-items:center;gap:4px;inline-size:100%;min-inline-size:0;padding-inline-end:8px}.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 .12s ease}.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(--_primary);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__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(--_primary);stroke-width:1.5;cursor:crosshair;opacity:0;transition:opacity .12s ease}.fly-gantt__row:hover .fly-gantt__connector{opacity:1}.fly-gantt__milestone{fill:var(--_primary);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--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-subtle);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"] }]
|
|
13254
|
-
}], 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 }] }], 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"] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], rowDblClick: [{ type: i0.Output, args: ["rowDblClick"] }], bodyRef: [{ type: i0.ViewChild, args: ['bodySvg', { isSignal: true }] }] } });
|
|
13255
|
-
|
|
13256
|
-
/**
|
|
13257
|
-
* Public data contract for {@link FlyGanttComponent}.
|
|
13258
|
-
*
|
|
13259
|
-
* The types here are deliberately **app-agnostic** — no Projects/Tasks vocabulary leaks in.
|
|
13260
|
-
* A consumer maps its own domain rows to {@link GanttRow}[] + {@link GanttDependency}[],
|
|
13261
|
-
* feeds them in, and reacts to the emitted change events by persisting itself. The Gantt
|
|
13262
|
-
* never calls a backend and never mutates its inputs.
|
|
13263
|
-
*/
|
|
13264
|
-
/** Immutable list of the supported zoom levels, coarsest-last. */
|
|
13265
|
-
const GANTT_ZOOMS = ['day', 'week', 'month', 'quarter'];
|
|
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"] }]
|
|
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 }] }] } });
|
|
13266
13579
|
|
|
13267
13580
|
// ── Bundled ISO 3166-1 country list ──────────────────────────────────────────
|
|
13268
13581
|
// Ships with <fly-survey-form> so a `Country` question renders a searchable
|
|
@@ -15962,7 +16275,7 @@ function projectSearch(search, handlers) {
|
|
|
15962
16275
|
: { filtersActive: search.filtersActive }),
|
|
15963
16276
|
hasFilters: !!search.onOpenFilters,
|
|
15964
16277
|
query: (text) => handlers.search()?.onQuery?.(text),
|
|
15965
|
-
openFilters: () => handlers.search()?.onOpenFilters?.(),
|
|
16278
|
+
openFilters: (trigger) => handlers.search()?.onOpenFilters?.(trigger),
|
|
15966
16279
|
};
|
|
15967
16280
|
}
|
|
15968
16281
|
function projectAction(action, handlers) {
|
|
@@ -15970,7 +16283,7 @@ function projectAction(action, handlers) {
|
|
|
15970
16283
|
return {
|
|
15971
16284
|
...stripHandlers(action),
|
|
15972
16285
|
menu: action.menu ? projectMenu(action, handlers) : null,
|
|
15973
|
-
run: () => handlers.action(id)?.onSelect?.(),
|
|
16286
|
+
run: (trigger) => handlers.action(id)?.onSelect?.(trigger),
|
|
15974
16287
|
};
|
|
15975
16288
|
}
|
|
15976
16289
|
function projectMenu(action, handlers) {
|
|
@@ -16964,7 +17277,11 @@ class FlyMagicActionsComponent {
|
|
|
16964
17277
|
this.toggleMenu(action.id, event.currentTarget);
|
|
16965
17278
|
return;
|
|
16966
17279
|
}
|
|
16967
|
-
action
|
|
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);
|
|
16968
17285
|
}
|
|
16969
17286
|
onMenuAction(action, value) {
|
|
16970
17287
|
// Late-bound: `action` is this render's view, but `select()` always resolves
|
|
@@ -17032,13 +17349,13 @@ class FlyMagicActionsComponent {
|
|
|
17032
17349
|
this.isOverflowing.set(!!el && el.scrollWidth - el.clientWidth > 1);
|
|
17033
17350
|
}
|
|
17034
17351
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyMagicActionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
17035
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyMagicActionsComponent, isStandalone: true, selector: "fly-magic-actions", inputs: { groups: { classPropertyName: "groups", publicName: "groups", isSignal: true, isRequired: true, transformFunction: null }, primary: { classPropertyName: "primary", publicName: "primary", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.dir": "i18n.direction()" } }, viewQueries: [{ propertyName: "rowRef", first: true, predicate: ["row"], descendants: true, isSignal: true }], ngImport: i0, template: "<!--\n Icon-only buttons carry the accessible name via [attr.aria-label] \u2014 the SVG\n glyph itself is aria-hidden (decorative, skill \u00A7accessibility rule 5). `kind:\n 'text'` actions rely on their own visible text instead. Disabled rows stay\n `aria-disabled` (never native `disabled`) so they remain focusable/hoverable \u2014\n a disabled action's whole point is to explain ITSELF via the auto-suffixed\n tooltip, which native `disabled` would suppress.\n\n The glyph is composed from ALLOWLISTED nodes bound as real attributes, never\n from `[innerHTML]`: `iconPath` is publisher-supplied, and an `<svg>` fragment\n is not a closed world (`<foreignObject>` is an HTML integration point, `<a\n xlink:href=\"javascript:\u2026\">` is live under the very click this button invites).\n `magic-actions-icon.ts` carries the full rationale; the shape below is its\n consequence \u2014 an attribute binding cannot introduce an element or a handler,\n so there is nothing left to sanitize.\n\n That block is authored ONCE, as `#actionButton`, and stamped by every place a\n contributed action is painted (group items and `primary`). It used to be\n inline in the group loop, which was fine while this component rendered groups\n only; with `primary` here too, a second copy would mean the next edit to the\n SECURITY-CRITICAL binding shape has to be made twice and can be made once.\n-->\n<!--\n `data-action` carries the publisher's action `id` verbatim \u2014 the one stable, LOCALE-INDEPENDENT\n handle on a rendered action. Every other identifying attribute here is translated\n (`aria-label`, the tooltip), so an e2e suite that targeted them had to assert in one language,\n and one that targeted a bespoke button lost its hook entirely when the action moved into the\n bar. That is not hypothetical: PPM's project-closure suite could not adopt the contract for\n exactly this reason. It is an ATTRIBUTE, not a class, so it never participates in styling, and\n `id` is already unique across the whole contribution (the projection enforces it), so it is\n unique in the DOM too.\n-->\n<ng-template #actionButton let-projected>\n <!--\n `let-projected` is `any` \u2014 Angular infers nothing for an `ngTemplateOutlet`\n context, and there is no built-in way to declare one. `asAction` is an\n identity function whose only job is to give this block a TYPED local, so\n every read below (`.kind`, `.disabled`, `.menu`) is checked by the template\n compiler instead of silently resolving to `undefined` on a typo. Without it\n the one place a `MagicBarActionView` is actually painted would be the one\n place its shape is not verified.\n -->\n @let action = asAction(projected);\n @if (action.kind === 'text') {\n <button\n type=\"button\"\n class=\"fma-btn fma-btn--text\"\n [attr.data-action]=\"action.id\"\n [attr.data-tone]=\"toneAttr(action)\"\n [attr.aria-disabled]=\"action.disabled ? 'true' : null\"\n [attr.aria-pressed]=\"pressedAttr(action)\"\n [flyTooltip]=\"tooltipFor(action)\"\n [flyTooltipDelay]=\"450\"\n (click)=\"activate(action, $event)\"\n >{{ resolvedLabel(action) }}</button>\n } @else {\n <button\n type=\"button\"\n class=\"fma-btn\"\n [class.fma-btn--badge]=\"hasBadge(action)\"\n [attr.data-action]=\"action.id\"\n [attr.data-tone]=\"toneAttr(action)\"\n [attr.aria-disabled]=\"action.disabled ? 'true' : null\"\n [attr.aria-pressed]=\"pressedAttr(action)\"\n [attr.aria-haspopup]=\"action.menu ? 'menu' : null\"\n [attr.aria-expanded]=\"action.menu ? (openMenuActionId() === action.id ? 'true' : 'false') : null\"\n [attr.aria-label]=\"action.menu ? triggerAriaLabel(action) : resolvedLabel(action)\"\n [flyTooltip]=\"tooltipFor(action)\"\n [flyTooltipDelay]=\"450\"\n (click)=\"activate(action, $event)\"\n >\n <svg\n class=\"fma-btn__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.8\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n @for (node of iconNodes(action); track $index) {\n @switch (node.tag) {\n @case ('path') {\n <svg:path\n [attr.d]=\"node.attrs['d']\"\n [attr.fill-rule]=\"node.attrs['fill-rule']\"\n [attr.clip-rule]=\"node.attrs['clip-rule']\"\n />\n }\n @case ('circle') {\n <svg:circle\n [attr.cx]=\"node.attrs['cx']\"\n [attr.cy]=\"node.attrs['cy']\"\n [attr.r]=\"node.attrs['r']\"\n />\n }\n @case ('rect') {\n <svg:rect\n [attr.x]=\"node.attrs['x']\"\n [attr.y]=\"node.attrs['y']\"\n [attr.width]=\"node.attrs['width']\"\n [attr.height]=\"node.attrs['height']\"\n [attr.rx]=\"node.attrs['rx']\"\n [attr.ry]=\"node.attrs['ry']\"\n />\n }\n @case ('ellipse') {\n <svg:ellipse\n [attr.cx]=\"node.attrs['cx']\"\n [attr.cy]=\"node.attrs['cy']\"\n [attr.rx]=\"node.attrs['rx']\"\n [attr.ry]=\"node.attrs['ry']\"\n />\n }\n @case ('line') {\n <svg:line\n [attr.x1]=\"node.attrs['x1']\"\n [attr.y1]=\"node.attrs['y1']\"\n [attr.x2]=\"node.attrs['x2']\"\n [attr.y2]=\"node.attrs['y2']\"\n />\n }\n @case ('polyline') {\n <svg:polyline [attr.points]=\"node.attrs['points']\" />\n }\n @case ('polygon') {\n <svg:polygon [attr.points]=\"node.attrs['points']\" />\n }\n }\n }\n </svg>\n @if (hasBadge(action)) {\n <span class=\"fma-btn__badge\" aria-hidden=\"true\"></span>\n }\n </button>\n }\n</ng-template>\n\n<!--\n `.fma` is the SCROLLING row and holds the contributed groups only. The accent\n CTA is a SIBLING of it, outside the fade mask and outside the scroll container,\n because it is the row's anchor \u2014 a call to action that dissolves into a\n gradient, or that you have to scroll sideways to reach, inverts the affordance.\n It is also what makes `isOverflowing` measurable at all: see the component.\n-->\n@if (groups().length) {\n <div #row class=\"fma\" [class.fma--overflow]=\"isOverflowing()\">\n @for (group of groups(); track group.id) {\n <div\n class=\"fma-group\"\n [class.fma-group--framed]=\"group.framed\"\n role=\"group\"\n [attr.aria-label]=\"i18n.t(group.labelKey)\"\n >\n @for (action of group.items; track action.id) {\n <ng-container\n [ngTemplateOutlet]=\"actionButton\"\n [ngTemplateOutletContext]=\"{ $implicit: action }\"\n />\n }\n </div>\n @if (group.framed) {\n <span class=\"fma-sep\" aria-hidden=\"true\"></span>\n }\n }\n </div>\n}\n\n<!--\n The accent CTA. The disc is a WRAPPER, not a modifier on the button, and that\n is load-bearing rather than cosmetic: `.fma-btn`'s hover/press feedback is an\n `animation` (`iconWiggle` / `iconBounce`), and an animation that touches\n `transform` beats a declared `transform` \u2014 so a hover lift authored on the\n button itself would simply never render. The wrapper owns the disc, the lift\n and the shadow; the button stays transparent and keeps the DS's own toolbar\n feedback. It re-points `--chrome-ink` rather than styling `.fma-btn` through a\n descendant selector so the glyph colour inherits the way custom properties do.\n-->\n@if (primary(); as p) {\n <span class=\"fma-primary\" [class.fma-primary--disabled]=\"p.disabled\">\n <ng-container\n [ngTemplateOutlet]=\"actionButton\"\n [ngTemplateOutletContext]=\"{ $implicit: p }\"\n />\n </span>\n}\n\n<!--\n ONE radio menu, hoisted out of the group loops (S1-review F9). Only one is open\n at a time (`openMenuActionId`), so a single instance is enough \u2014 and rendering\n it beside its button would insert a non-`.fma-btn` element into the group,\n silently shifting every later button's `:nth-child` index and with it the\n `toolbarPopIn` stagger delay the `.fma-btn` rule assigns. Keeping every\n staggered parent's children homogeneous is what makes that rule's comment true.\n It portals itself to <body>, so its position is unaffected by where it is\n declared.\n-->\n@if (openAction(); as open) {\n <fly-context-menu\n [anchor]=\"menuAnchorEl()\"\n [sections]=\"menuSections(open)\"\n (action)=\"onMenuAction(open, $event)\"\n (closed)=\"closeMenu()\"\n />\n}\n", styles: [":host{display:flex;align-items:center;gap:8px;min-inline-size:0}.fma{display:flex;align-items:center;gap:8px;flex:0 1 auto;min-inline-size:0;overflow:hidden;flex-wrap:nowrap}.fma--overflow{overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none;-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent 100%);mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent 100%)}.fma--overflow::-webkit-scrollbar{display:none}:host([dir=rtl]) .fma--overflow{-webkit-mask-image:linear-gradient(to left,#000 calc(100% - 28px),transparent 100%);mask-image:linear-gradient(to left,#000 calc(100% - 28px),transparent 100%)}.fma-group{display:flex;align-items:center;gap:8px;flex:none}.fma-group--framed{gap:2px}.fma-sep{flex:0 0 auto;inline-size:1px;block-size:18px;background:var(--w14)}.fma-btn{position:relative;flex:0 0 auto;inline-size:30px;block-size:30px;display:grid;place-items:center;border:0;border-radius:50%;background:transparent;color:var(--chrome-ink);cursor:pointer;padding:0;animation:toolbarPopIn .3s var(--nova-ease-structural) both}.fma-btn:nth-child(1){animation-delay:0ms}.fma-btn:nth-child(2){animation-delay:40ms}.fma-btn:nth-child(3){animation-delay:80ms}.fma-btn:nth-child(4){animation-delay:.12s}.fma-btn:nth-child(5){animation-delay:.16s}.fma-btn:nth-child(6){animation-delay:.2s}.fma-btn:nth-child(7){animation-delay:.24s}.fma-btn:nth-child(8){animation-delay:.28s}.fma-btn:nth-child(9){animation-delay:.32s}.fma-btn:nth-child(10){animation-delay:.36s}.fma-btn:nth-child(11){animation-delay:.4s}.fma-btn:nth-child(12){animation-delay:.44s}.fma-btn:nth-child(n+13){animation-delay:.44s}.fma-btn:not([aria-disabled=true]):hover{background:var(--w1);color:var(--chrome-ink-hover);animation:iconWiggle .5s ease}.fma-btn:not([aria-disabled=true]):active{animation:iconBounce .38s var(--nova-ease-overshoot)}.fma-btn:focus-visible{outline:3px solid var(--accent);outline-offset:3px;border-radius:inherit}.fma-btn[data-tone=danger]{color:var(--sys-red)}.fma-btn[data-tone=danger]:not([aria-disabled=true]):hover{background:color-mix(in oklab,var(--sys-red) 18%,transparent);color:var(--sys-red)}.fma-btn[data-tone=success]{color:var(--sys-green)}.fma-btn[data-tone=success]:not([aria-disabled=true]):hover{background:color-mix(in oklab,var(--sys-green) 18%,transparent);color:var(--sys-green)}.fma-btn[aria-pressed=true]{background:var(--tint-sel);color:var(--chrome-ink-hover)}.fma-btn[aria-pressed=true][data-tone=success]{background:color-mix(in oklab,var(--sys-green) 22%,transparent);color:var(--sys-green)}.fma-btn[aria-disabled=true]{opacity:.38;filter:saturate(.6);animation:none;cursor:default}.fma-btn__icon{inline-size:16px;block-size:16px;pointer-events:none}.fma-btn__badge{position:absolute;inset-block-start:3px;inset-inline-end:3px;inline-size:7px;block-size:7px;border-radius:50%;background:var(--accent);pointer-events:none}.fma-primary{--chrome-ink: var(--on-accent);--chrome-ink-hover: var(--on-accent);flex:none;display:grid;place-items:center;inline-size:30px;block-size:30px;border-radius:50%;background:var(--accent);transition:box-shadow var(--nova-duration-hover) var(--nova-ease-micro),transform var(--nova-duration-hover) var(--nova-ease-micro)}.fma-primary:hover{box-shadow:0 8px 20px var(--tint-sel);transform:translateY(-1px)}.fma-primary--disabled{opacity:.38;filter:saturate(.6)}.fma-primary--disabled:hover{box-shadow:none;transform:none}.fma-btn--text{inline-size:auto;block-size:30px;border-radius:99px;padding-inline:12px;font-family:inherit;font-size:12.5px;font-weight:600;line-height:1;white-space:nowrap}@media(pointer:coarse){.fma-btn{inline-size:44px;block-size:44px}.fma-btn--text{inline-size:auto;min-inline-size:44px}.fma-primary{inline-size:44px;block-size:44px}}\n"], dependencies: [{ kind: "component", type: ContextMenuComponent, selector: "fly-context-menu", inputs: ["x", "y", "anchor", "align", "placement", "sections", "boundary"], outputs: ["action", "closed"] }, { kind: "directive", type: FlyTooltipDirective, selector: "[flyTooltip], [data-tooltip]", inputs: ["flyTooltip", "flyTooltipPlacement", "flyTooltipDisabled", "flyTooltipDelay"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
17352
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyMagicActionsComponent, isStandalone: true, selector: "fly-magic-actions", inputs: { groups: { classPropertyName: "groups", publicName: "groups", isSignal: true, isRequired: true, transformFunction: null }, primary: { classPropertyName: "primary", publicName: "primary", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.dir": "i18n.direction()" } }, viewQueries: [{ propertyName: "rowRef", first: true, predicate: ["row"], descendants: true, isSignal: true }], ngImport: i0, template: "<!--\r\n Icon-only buttons carry the accessible name via [attr.aria-label] \u2014 the SVG\r\n glyph itself is aria-hidden (decorative, skill \u00A7accessibility rule 5). `kind:\r\n 'text'` actions rely on their own visible text instead. Disabled rows stay\r\n `aria-disabled` (never native `disabled`) so they remain focusable/hoverable \u2014\r\n a disabled action's whole point is to explain ITSELF via the auto-suffixed\r\n tooltip, which native `disabled` would suppress.\r\n\r\n The glyph is composed from ALLOWLISTED nodes bound as real attributes, never\r\n from `[innerHTML]`: `iconPath` is publisher-supplied, and an `<svg>` fragment\r\n is not a closed world (`<foreignObject>` is an HTML integration point, `<a\r\n xlink:href=\"javascript:\u2026\">` is live under the very click this button invites).\r\n `magic-actions-icon.ts` carries the full rationale; the shape below is its\r\n consequence \u2014 an attribute binding cannot introduce an element or a handler,\r\n so there is nothing left to sanitize.\r\n\r\n That block is authored ONCE, as `#actionButton`, and stamped by every place a\r\n contributed action is painted (group items and `primary`). It used to be\r\n inline in the group loop, which was fine while this component rendered groups\r\n only; with `primary` here too, a second copy would mean the next edit to the\r\n SECURITY-CRITICAL binding shape has to be made twice and can be made once.\r\n-->\r\n<!--\r\n `data-action` carries the publisher's action `id` verbatim \u2014 the one stable, LOCALE-INDEPENDENT\r\n handle on a rendered action. Every other identifying attribute here is translated\r\n (`aria-label`, the tooltip), so an e2e suite that targeted them had to assert in one language,\r\n and one that targeted a bespoke button lost its hook entirely when the action moved into the\r\n bar. That is not hypothetical: PPM's project-closure suite could not adopt the contract for\r\n exactly this reason. It is an ATTRIBUTE, not a class, so it never participates in styling, and\r\n `id` is already unique across the whole contribution (the projection enforces it), so it is\r\n unique in the DOM too.\r\n-->\r\n<ng-template #actionButton let-projected>\r\n <!--\r\n `let-projected` is `any` \u2014 Angular infers nothing for an `ngTemplateOutlet`\r\n context, and there is no built-in way to declare one. `asAction` is an\r\n identity function whose only job is to give this block a TYPED local, so\r\n every read below (`.kind`, `.disabled`, `.menu`) is checked by the template\r\n compiler instead of silently resolving to `undefined` on a typo. Without it\r\n the one place a `MagicBarActionView` is actually painted would be the one\r\n place its shape is not verified.\r\n -->\r\n @let action = asAction(projected);\r\n @if (action.kind === 'text') {\r\n <button\r\n type=\"button\"\r\n class=\"fma-btn fma-btn--text\"\r\n [attr.data-action]=\"action.id\"\r\n [attr.data-tone]=\"toneAttr(action)\"\r\n [attr.aria-disabled]=\"action.disabled ? 'true' : null\"\r\n [attr.aria-pressed]=\"pressedAttr(action)\"\r\n [flyTooltip]=\"tooltipFor(action)\"\r\n [flyTooltipDelay]=\"450\"\r\n (click)=\"activate(action, $event)\"\r\n >{{ resolvedLabel(action) }}</button>\r\n } @else {\r\n <button\r\n type=\"button\"\r\n class=\"fma-btn\"\r\n [class.fma-btn--badge]=\"hasBadge(action)\"\r\n [attr.data-action]=\"action.id\"\r\n [attr.data-tone]=\"toneAttr(action)\"\r\n [attr.aria-disabled]=\"action.disabled ? 'true' : null\"\r\n [attr.aria-pressed]=\"pressedAttr(action)\"\r\n [attr.aria-haspopup]=\"action.menu ? 'menu' : null\"\r\n [attr.aria-expanded]=\"action.menu ? (openMenuActionId() === action.id ? 'true' : 'false') : null\"\r\n [attr.aria-label]=\"action.menu ? triggerAriaLabel(action) : resolvedLabel(action)\"\r\n [flyTooltip]=\"tooltipFor(action)\"\r\n [flyTooltipDelay]=\"450\"\r\n (click)=\"activate(action, $event)\"\r\n >\r\n <svg\r\n class=\"fma-btn__icon\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"1.8\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n aria-hidden=\"true\"\r\n >\r\n @for (node of iconNodes(action); track $index) {\r\n @switch (node.tag) {\r\n @case ('path') {\r\n <svg:path\r\n [attr.d]=\"node.attrs['d']\"\r\n [attr.fill-rule]=\"node.attrs['fill-rule']\"\r\n [attr.clip-rule]=\"node.attrs['clip-rule']\"\r\n />\r\n }\r\n @case ('circle') {\r\n <svg:circle\r\n [attr.cx]=\"node.attrs['cx']\"\r\n [attr.cy]=\"node.attrs['cy']\"\r\n [attr.r]=\"node.attrs['r']\"\r\n />\r\n }\r\n @case ('rect') {\r\n <svg:rect\r\n [attr.x]=\"node.attrs['x']\"\r\n [attr.y]=\"node.attrs['y']\"\r\n [attr.width]=\"node.attrs['width']\"\r\n [attr.height]=\"node.attrs['height']\"\r\n [attr.rx]=\"node.attrs['rx']\"\r\n [attr.ry]=\"node.attrs['ry']\"\r\n />\r\n }\r\n @case ('ellipse') {\r\n <svg:ellipse\r\n [attr.cx]=\"node.attrs['cx']\"\r\n [attr.cy]=\"node.attrs['cy']\"\r\n [attr.rx]=\"node.attrs['rx']\"\r\n [attr.ry]=\"node.attrs['ry']\"\r\n />\r\n }\r\n @case ('line') {\r\n <svg:line\r\n [attr.x1]=\"node.attrs['x1']\"\r\n [attr.y1]=\"node.attrs['y1']\"\r\n [attr.x2]=\"node.attrs['x2']\"\r\n [attr.y2]=\"node.attrs['y2']\"\r\n />\r\n }\r\n @case ('polyline') {\r\n <svg:polyline [attr.points]=\"node.attrs['points']\" />\r\n }\r\n @case ('polygon') {\r\n <svg:polygon [attr.points]=\"node.attrs['points']\" />\r\n }\r\n }\r\n }\r\n </svg>\r\n @if (hasBadge(action)) {\r\n <span class=\"fma-btn__badge\" aria-hidden=\"true\"></span>\r\n }\r\n </button>\r\n }\r\n</ng-template>\r\n\r\n<!--\r\n `.fma` is the SCROLLING row and holds the contributed groups only. The accent\r\n CTA is a SIBLING of it, outside the fade mask and outside the scroll container,\r\n because it is the row's anchor \u2014 a call to action that dissolves into a\r\n gradient, or that you have to scroll sideways to reach, inverts the affordance.\r\n It is also what makes `isOverflowing` measurable at all: see the component.\r\n-->\r\n@if (groups().length) {\r\n <div #row class=\"fma\" [class.fma--overflow]=\"isOverflowing()\">\r\n @for (group of groups(); track group.id) {\r\n <div\r\n class=\"fma-group\"\r\n [class.fma-group--framed]=\"group.framed\"\r\n role=\"group\"\r\n [attr.aria-label]=\"i18n.t(group.labelKey)\"\r\n >\r\n @for (action of group.items; track action.id) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"actionButton\"\r\n [ngTemplateOutletContext]=\"{ $implicit: action }\"\r\n />\r\n }\r\n </div>\r\n @if (group.framed) {\r\n <span class=\"fma-sep\" aria-hidden=\"true\"></span>\r\n }\r\n }\r\n </div>\r\n}\r\n\r\n<!--\r\n The accent CTA. The disc is a WRAPPER, not a modifier on the button, and that\r\n is load-bearing rather than cosmetic: `.fma-btn`'s hover/press feedback is an\r\n `animation` (`iconWiggle` / `iconBounce`), and an animation that touches\r\n `transform` beats a declared `transform` \u2014 so a hover lift authored on the\r\n button itself would simply never render. The wrapper owns the disc, the lift\r\n and the shadow; the button stays transparent and keeps the DS's own toolbar\r\n feedback. It re-points `--chrome-ink` rather than styling `.fma-btn` through a\r\n descendant selector so the glyph colour inherits the way custom properties do.\r\n-->\r\n@if (primary(); as p) {\r\n <span class=\"fma-primary\" [class.fma-primary--disabled]=\"p.disabled\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"actionButton\"\r\n [ngTemplateOutletContext]=\"{ $implicit: p }\"\r\n />\r\n </span>\r\n}\r\n\r\n<!--\r\n ONE radio menu, hoisted out of the group loops (S1-review F9). Only one is open\r\n at a time (`openMenuActionId`), so a single instance is enough \u2014 and rendering\r\n it beside its button would insert a non-`.fma-btn` element into the group,\r\n silently shifting every later button's `:nth-child` index and with it the\r\n `toolbarPopIn` stagger delay the `.fma-btn` rule assigns. Keeping every\r\n staggered parent's children homogeneous is what makes that rule's comment true.\r\n It portals itself to <body>, so its position is unaffected by where it is\r\n declared.\r\n-->\r\n@if (openAction(); as open) {\r\n <fly-context-menu\r\n [anchor]=\"menuAnchorEl()\"\r\n [sections]=\"menuSections(open)\"\r\n (action)=\"onMenuAction(open, $event)\"\r\n (closed)=\"closeMenu()\"\r\n />\r\n}\r\n", styles: [":host{display:flex;align-items:center;gap:8px;min-inline-size:0}.fma{display:flex;align-items:center;gap:8px;flex:0 1 auto;min-inline-size:0;overflow:hidden;flex-wrap:nowrap}.fma--overflow{overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none;-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent 100%);mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent 100%)}.fma--overflow::-webkit-scrollbar{display:none}:host([dir=rtl]) .fma--overflow{-webkit-mask-image:linear-gradient(to left,#000 calc(100% - 28px),transparent 100%);mask-image:linear-gradient(to left,#000 calc(100% - 28px),transparent 100%)}.fma-group{display:flex;align-items:center;gap:8px;flex:none}.fma-group--framed{gap:2px}.fma-sep{flex:0 0 auto;inline-size:1px;block-size:18px;background:var(--w14)}.fma-btn{position:relative;flex:0 0 auto;inline-size:30px;block-size:30px;display:grid;place-items:center;border:0;border-radius:50%;background:transparent;color:var(--chrome-ink);cursor:pointer;padding:0;animation:toolbarPopIn .3s var(--nova-ease-structural) both}.fma-btn:nth-child(1){animation-delay:0ms}.fma-btn:nth-child(2){animation-delay:40ms}.fma-btn:nth-child(3){animation-delay:80ms}.fma-btn:nth-child(4){animation-delay:.12s}.fma-btn:nth-child(5){animation-delay:.16s}.fma-btn:nth-child(6){animation-delay:.2s}.fma-btn:nth-child(7){animation-delay:.24s}.fma-btn:nth-child(8){animation-delay:.28s}.fma-btn:nth-child(9){animation-delay:.32s}.fma-btn:nth-child(10){animation-delay:.36s}.fma-btn:nth-child(11){animation-delay:.4s}.fma-btn:nth-child(12){animation-delay:.44s}.fma-btn:nth-child(n+13){animation-delay:.44s}.fma-btn:not([aria-disabled=true]):hover{background:var(--w1);color:var(--chrome-ink-hover);animation:iconWiggle .5s ease}.fma-btn:not([aria-disabled=true]):active{animation:iconBounce .38s var(--nova-ease-overshoot)}.fma-btn:focus-visible{outline:3px solid var(--accent);outline-offset:3px;border-radius:inherit}.fma-btn[data-tone=danger]{color:var(--sys-red)}.fma-btn[data-tone=danger]:not([aria-disabled=true]):hover{background:color-mix(in oklab,var(--sys-red) 18%,transparent);color:var(--sys-red)}.fma-btn[data-tone=success]{color:var(--sys-green)}.fma-btn[data-tone=success]:not([aria-disabled=true]):hover{background:color-mix(in oklab,var(--sys-green) 18%,transparent);color:var(--sys-green)}.fma-btn[aria-pressed=true]{background:var(--tint-sel);color:var(--chrome-ink-hover)}.fma-btn[aria-pressed=true][data-tone=success]{background:color-mix(in oklab,var(--sys-green) 22%,transparent);color:var(--sys-green)}.fma-btn[aria-disabled=true]{opacity:.38;filter:saturate(.6);animation:none;cursor:default}.fma-btn__icon{inline-size:16px;block-size:16px;pointer-events:none}.fma-btn__badge{position:absolute;inset-block-start:3px;inset-inline-end:3px;inline-size:7px;block-size:7px;border-radius:50%;background:var(--accent);pointer-events:none}.fma-primary{--chrome-ink: var(--on-accent);--chrome-ink-hover: var(--on-accent);flex:none;display:grid;place-items:center;inline-size:30px;block-size:30px;border-radius:50%;background:var(--accent);transition:box-shadow var(--nova-duration-hover) var(--nova-ease-micro),transform var(--nova-duration-hover) var(--nova-ease-micro)}.fma-primary:hover{box-shadow:0 8px 20px var(--tint-sel);transform:translateY(-1px)}.fma-primary--disabled{opacity:.38;filter:saturate(.6)}.fma-primary--disabled:hover{box-shadow:none;transform:none}.fma-btn--text{inline-size:auto;block-size:30px;border-radius:99px;padding-inline:12px;font-family:inherit;font-size:12.5px;font-weight:600;line-height:1;white-space:nowrap}@media(pointer:coarse){.fma-btn{inline-size:44px;block-size:44px}.fma-btn--text{inline-size:auto;min-inline-size:44px}.fma-primary{inline-size:44px;block-size:44px}}\n"], dependencies: [{ kind: "component", type: ContextMenuComponent, selector: "fly-context-menu", inputs: ["x", "y", "anchor", "align", "placement", "sections", "boundary"], outputs: ["action", "closed"] }, { kind: "directive", type: FlyTooltipDirective, selector: "[flyTooltip], [data-tooltip]", inputs: ["flyTooltip", "flyTooltipPlacement", "flyTooltipDisabled", "flyTooltipDelay"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
17036
17353
|
}
|
|
17037
17354
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyMagicActionsComponent, decorators: [{
|
|
17038
17355
|
type: Component,
|
|
17039
17356
|
args: [{ selector: 'fly-magic-actions', standalone: true, imports: [ContextMenuComponent, FlyTooltipDirective, NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
17040
17357
|
'[attr.dir]': 'i18n.direction()',
|
|
17041
|
-
}, template: "<!--\n Icon-only buttons carry the accessible name via [attr.aria-label] \u2014 the SVG\n glyph itself is aria-hidden (decorative, skill \u00A7accessibility rule 5). `kind:\n 'text'` actions rely on their own visible text instead. Disabled rows stay\n `aria-disabled` (never native `disabled`) so they remain focusable/hoverable \u2014\n a disabled action's whole point is to explain ITSELF via the auto-suffixed\n tooltip, which native `disabled` would suppress.\n\n The glyph is composed from ALLOWLISTED nodes bound as real attributes, never\n from `[innerHTML]`: `iconPath` is publisher-supplied, and an `<svg>` fragment\n is not a closed world (`<foreignObject>` is an HTML integration point, `<a\n xlink:href=\"javascript:\u2026\">` is live under the very click this button invites).\n `magic-actions-icon.ts` carries the full rationale; the shape below is its\n consequence \u2014 an attribute binding cannot introduce an element or a handler,\n so there is nothing left to sanitize.\n\n That block is authored ONCE, as `#actionButton`, and stamped by every place a\n contributed action is painted (group items and `primary`). It used to be\n inline in the group loop, which was fine while this component rendered groups\n only; with `primary` here too, a second copy would mean the next edit to the\n SECURITY-CRITICAL binding shape has to be made twice and can be made once.\n-->\n<!--\n `data-action` carries the publisher's action `id` verbatim \u2014 the one stable, LOCALE-INDEPENDENT\n handle on a rendered action. Every other identifying attribute here is translated\n (`aria-label`, the tooltip), so an e2e suite that targeted them had to assert in one language,\n and one that targeted a bespoke button lost its hook entirely when the action moved into the\n bar. That is not hypothetical: PPM's project-closure suite could not adopt the contract for\n exactly this reason. It is an ATTRIBUTE, not a class, so it never participates in styling, and\n `id` is already unique across the whole contribution (the projection enforces it), so it is\n unique in the DOM too.\n-->\n<ng-template #actionButton let-projected>\n <!--\n `let-projected` is `any` \u2014 Angular infers nothing for an `ngTemplateOutlet`\n context, and there is no built-in way to declare one. `asAction` is an\n identity function whose only job is to give this block a TYPED local, so\n every read below (`.kind`, `.disabled`, `.menu`) is checked by the template\n compiler instead of silently resolving to `undefined` on a typo. Without it\n the one place a `MagicBarActionView` is actually painted would be the one\n place its shape is not verified.\n -->\n @let action = asAction(projected);\n @if (action.kind === 'text') {\n <button\n type=\"button\"\n class=\"fma-btn fma-btn--text\"\n [attr.data-action]=\"action.id\"\n [attr.data-tone]=\"toneAttr(action)\"\n [attr.aria-disabled]=\"action.disabled ? 'true' : null\"\n [attr.aria-pressed]=\"pressedAttr(action)\"\n [flyTooltip]=\"tooltipFor(action)\"\n [flyTooltipDelay]=\"450\"\n (click)=\"activate(action, $event)\"\n >{{ resolvedLabel(action) }}</button>\n } @else {\n <button\n type=\"button\"\n class=\"fma-btn\"\n [class.fma-btn--badge]=\"hasBadge(action)\"\n [attr.data-action]=\"action.id\"\n [attr.data-tone]=\"toneAttr(action)\"\n [attr.aria-disabled]=\"action.disabled ? 'true' : null\"\n [attr.aria-pressed]=\"pressedAttr(action)\"\n [attr.aria-haspopup]=\"action.menu ? 'menu' : null\"\n [attr.aria-expanded]=\"action.menu ? (openMenuActionId() === action.id ? 'true' : 'false') : null\"\n [attr.aria-label]=\"action.menu ? triggerAriaLabel(action) : resolvedLabel(action)\"\n [flyTooltip]=\"tooltipFor(action)\"\n [flyTooltipDelay]=\"450\"\n (click)=\"activate(action, $event)\"\n >\n <svg\n class=\"fma-btn__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.8\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n @for (node of iconNodes(action); track $index) {\n @switch (node.tag) {\n @case ('path') {\n <svg:path\n [attr.d]=\"node.attrs['d']\"\n [attr.fill-rule]=\"node.attrs['fill-rule']\"\n [attr.clip-rule]=\"node.attrs['clip-rule']\"\n />\n }\n @case ('circle') {\n <svg:circle\n [attr.cx]=\"node.attrs['cx']\"\n [attr.cy]=\"node.attrs['cy']\"\n [attr.r]=\"node.attrs['r']\"\n />\n }\n @case ('rect') {\n <svg:rect\n [attr.x]=\"node.attrs['x']\"\n [attr.y]=\"node.attrs['y']\"\n [attr.width]=\"node.attrs['width']\"\n [attr.height]=\"node.attrs['height']\"\n [attr.rx]=\"node.attrs['rx']\"\n [attr.ry]=\"node.attrs['ry']\"\n />\n }\n @case ('ellipse') {\n <svg:ellipse\n [attr.cx]=\"node.attrs['cx']\"\n [attr.cy]=\"node.attrs['cy']\"\n [attr.rx]=\"node.attrs['rx']\"\n [attr.ry]=\"node.attrs['ry']\"\n />\n }\n @case ('line') {\n <svg:line\n [attr.x1]=\"node.attrs['x1']\"\n [attr.y1]=\"node.attrs['y1']\"\n [attr.x2]=\"node.attrs['x2']\"\n [attr.y2]=\"node.attrs['y2']\"\n />\n }\n @case ('polyline') {\n <svg:polyline [attr.points]=\"node.attrs['points']\" />\n }\n @case ('polygon') {\n <svg:polygon [attr.points]=\"node.attrs['points']\" />\n }\n }\n }\n </svg>\n @if (hasBadge(action)) {\n <span class=\"fma-btn__badge\" aria-hidden=\"true\"></span>\n }\n </button>\n }\n</ng-template>\n\n<!--\n `.fma` is the SCROLLING row and holds the contributed groups only. The accent\n CTA is a SIBLING of it, outside the fade mask and outside the scroll container,\n because it is the row's anchor \u2014 a call to action that dissolves into a\n gradient, or that you have to scroll sideways to reach, inverts the affordance.\n It is also what makes `isOverflowing` measurable at all: see the component.\n-->\n@if (groups().length) {\n <div #row class=\"fma\" [class.fma--overflow]=\"isOverflowing()\">\n @for (group of groups(); track group.id) {\n <div\n class=\"fma-group\"\n [class.fma-group--framed]=\"group.framed\"\n role=\"group\"\n [attr.aria-label]=\"i18n.t(group.labelKey)\"\n >\n @for (action of group.items; track action.id) {\n <ng-container\n [ngTemplateOutlet]=\"actionButton\"\n [ngTemplateOutletContext]=\"{ $implicit: action }\"\n />\n }\n </div>\n @if (group.framed) {\n <span class=\"fma-sep\" aria-hidden=\"true\"></span>\n }\n }\n </div>\n}\n\n<!--\n The accent CTA. The disc is a WRAPPER, not a modifier on the button, and that\n is load-bearing rather than cosmetic: `.fma-btn`'s hover/press feedback is an\n `animation` (`iconWiggle` / `iconBounce`), and an animation that touches\n `transform` beats a declared `transform` \u2014 so a hover lift authored on the\n button itself would simply never render. The wrapper owns the disc, the lift\n and the shadow; the button stays transparent and keeps the DS's own toolbar\n feedback. It re-points `--chrome-ink` rather than styling `.fma-btn` through a\n descendant selector so the glyph colour inherits the way custom properties do.\n-->\n@if (primary(); as p) {\n <span class=\"fma-primary\" [class.fma-primary--disabled]=\"p.disabled\">\n <ng-container\n [ngTemplateOutlet]=\"actionButton\"\n [ngTemplateOutletContext]=\"{ $implicit: p }\"\n />\n </span>\n}\n\n<!--\n ONE radio menu, hoisted out of the group loops (S1-review F9). Only one is open\n at a time (`openMenuActionId`), so a single instance is enough \u2014 and rendering\n it beside its button would insert a non-`.fma-btn` element into the group,\n silently shifting every later button's `:nth-child` index and with it the\n `toolbarPopIn` stagger delay the `.fma-btn` rule assigns. Keeping every\n staggered parent's children homogeneous is what makes that rule's comment true.\n It portals itself to <body>, so its position is unaffected by where it is\n declared.\n-->\n@if (openAction(); as open) {\n <fly-context-menu\n [anchor]=\"menuAnchorEl()\"\n [sections]=\"menuSections(open)\"\n (action)=\"onMenuAction(open, $event)\"\n (closed)=\"closeMenu()\"\n />\n}\n", styles: [":host{display:flex;align-items:center;gap:8px;min-inline-size:0}.fma{display:flex;align-items:center;gap:8px;flex:0 1 auto;min-inline-size:0;overflow:hidden;flex-wrap:nowrap}.fma--overflow{overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none;-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent 100%);mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent 100%)}.fma--overflow::-webkit-scrollbar{display:none}:host([dir=rtl]) .fma--overflow{-webkit-mask-image:linear-gradient(to left,#000 calc(100% - 28px),transparent 100%);mask-image:linear-gradient(to left,#000 calc(100% - 28px),transparent 100%)}.fma-group{display:flex;align-items:center;gap:8px;flex:none}.fma-group--framed{gap:2px}.fma-sep{flex:0 0 auto;inline-size:1px;block-size:18px;background:var(--w14)}.fma-btn{position:relative;flex:0 0 auto;inline-size:30px;block-size:30px;display:grid;place-items:center;border:0;border-radius:50%;background:transparent;color:var(--chrome-ink);cursor:pointer;padding:0;animation:toolbarPopIn .3s var(--nova-ease-structural) both}.fma-btn:nth-child(1){animation-delay:0ms}.fma-btn:nth-child(2){animation-delay:40ms}.fma-btn:nth-child(3){animation-delay:80ms}.fma-btn:nth-child(4){animation-delay:.12s}.fma-btn:nth-child(5){animation-delay:.16s}.fma-btn:nth-child(6){animation-delay:.2s}.fma-btn:nth-child(7){animation-delay:.24s}.fma-btn:nth-child(8){animation-delay:.28s}.fma-btn:nth-child(9){animation-delay:.32s}.fma-btn:nth-child(10){animation-delay:.36s}.fma-btn:nth-child(11){animation-delay:.4s}.fma-btn:nth-child(12){animation-delay:.44s}.fma-btn:nth-child(n+13){animation-delay:.44s}.fma-btn:not([aria-disabled=true]):hover{background:var(--w1);color:var(--chrome-ink-hover);animation:iconWiggle .5s ease}.fma-btn:not([aria-disabled=true]):active{animation:iconBounce .38s var(--nova-ease-overshoot)}.fma-btn:focus-visible{outline:3px solid var(--accent);outline-offset:3px;border-radius:inherit}.fma-btn[data-tone=danger]{color:var(--sys-red)}.fma-btn[data-tone=danger]:not([aria-disabled=true]):hover{background:color-mix(in oklab,var(--sys-red) 18%,transparent);color:var(--sys-red)}.fma-btn[data-tone=success]{color:var(--sys-green)}.fma-btn[data-tone=success]:not([aria-disabled=true]):hover{background:color-mix(in oklab,var(--sys-green) 18%,transparent);color:var(--sys-green)}.fma-btn[aria-pressed=true]{background:var(--tint-sel);color:var(--chrome-ink-hover)}.fma-btn[aria-pressed=true][data-tone=success]{background:color-mix(in oklab,var(--sys-green) 22%,transparent);color:var(--sys-green)}.fma-btn[aria-disabled=true]{opacity:.38;filter:saturate(.6);animation:none;cursor:default}.fma-btn__icon{inline-size:16px;block-size:16px;pointer-events:none}.fma-btn__badge{position:absolute;inset-block-start:3px;inset-inline-end:3px;inline-size:7px;block-size:7px;border-radius:50%;background:var(--accent);pointer-events:none}.fma-primary{--chrome-ink: var(--on-accent);--chrome-ink-hover: var(--on-accent);flex:none;display:grid;place-items:center;inline-size:30px;block-size:30px;border-radius:50%;background:var(--accent);transition:box-shadow var(--nova-duration-hover) var(--nova-ease-micro),transform var(--nova-duration-hover) var(--nova-ease-micro)}.fma-primary:hover{box-shadow:0 8px 20px var(--tint-sel);transform:translateY(-1px)}.fma-primary--disabled{opacity:.38;filter:saturate(.6)}.fma-primary--disabled:hover{box-shadow:none;transform:none}.fma-btn--text{inline-size:auto;block-size:30px;border-radius:99px;padding-inline:12px;font-family:inherit;font-size:12.5px;font-weight:600;line-height:1;white-space:nowrap}@media(pointer:coarse){.fma-btn{inline-size:44px;block-size:44px}.fma-btn--text{inline-size:auto;min-inline-size:44px}.fma-primary{inline-size:44px;block-size:44px}}\n"] }]
|
|
17358
|
+
}, template: "<!--\r\n Icon-only buttons carry the accessible name via [attr.aria-label] \u2014 the SVG\r\n glyph itself is aria-hidden (decorative, skill \u00A7accessibility rule 5). `kind:\r\n 'text'` actions rely on their own visible text instead. Disabled rows stay\r\n `aria-disabled` (never native `disabled`) so they remain focusable/hoverable \u2014\r\n a disabled action's whole point is to explain ITSELF via the auto-suffixed\r\n tooltip, which native `disabled` would suppress.\r\n\r\n The glyph is composed from ALLOWLISTED nodes bound as real attributes, never\r\n from `[innerHTML]`: `iconPath` is publisher-supplied, and an `<svg>` fragment\r\n is not a closed world (`<foreignObject>` is an HTML integration point, `<a\r\n xlink:href=\"javascript:\u2026\">` is live under the very click this button invites).\r\n `magic-actions-icon.ts` carries the full rationale; the shape below is its\r\n consequence \u2014 an attribute binding cannot introduce an element or a handler,\r\n so there is nothing left to sanitize.\r\n\r\n That block is authored ONCE, as `#actionButton`, and stamped by every place a\r\n contributed action is painted (group items and `primary`). It used to be\r\n inline in the group loop, which was fine while this component rendered groups\r\n only; with `primary` here too, a second copy would mean the next edit to the\r\n SECURITY-CRITICAL binding shape has to be made twice and can be made once.\r\n-->\r\n<!--\r\n `data-action` carries the publisher's action `id` verbatim \u2014 the one stable, LOCALE-INDEPENDENT\r\n handle on a rendered action. Every other identifying attribute here is translated\r\n (`aria-label`, the tooltip), so an e2e suite that targeted them had to assert in one language,\r\n and one that targeted a bespoke button lost its hook entirely when the action moved into the\r\n bar. That is not hypothetical: PPM's project-closure suite could not adopt the contract for\r\n exactly this reason. It is an ATTRIBUTE, not a class, so it never participates in styling, and\r\n `id` is already unique across the whole contribution (the projection enforces it), so it is\r\n unique in the DOM too.\r\n-->\r\n<ng-template #actionButton let-projected>\r\n <!--\r\n `let-projected` is `any` \u2014 Angular infers nothing for an `ngTemplateOutlet`\r\n context, and there is no built-in way to declare one. `asAction` is an\r\n identity function whose only job is to give this block a TYPED local, so\r\n every read below (`.kind`, `.disabled`, `.menu`) is checked by the template\r\n compiler instead of silently resolving to `undefined` on a typo. Without it\r\n the one place a `MagicBarActionView` is actually painted would be the one\r\n place its shape is not verified.\r\n -->\r\n @let action = asAction(projected);\r\n @if (action.kind === 'text') {\r\n <button\r\n type=\"button\"\r\n class=\"fma-btn fma-btn--text\"\r\n [attr.data-action]=\"action.id\"\r\n [attr.data-tone]=\"toneAttr(action)\"\r\n [attr.aria-disabled]=\"action.disabled ? 'true' : null\"\r\n [attr.aria-pressed]=\"pressedAttr(action)\"\r\n [flyTooltip]=\"tooltipFor(action)\"\r\n [flyTooltipDelay]=\"450\"\r\n (click)=\"activate(action, $event)\"\r\n >{{ resolvedLabel(action) }}</button>\r\n } @else {\r\n <button\r\n type=\"button\"\r\n class=\"fma-btn\"\r\n [class.fma-btn--badge]=\"hasBadge(action)\"\r\n [attr.data-action]=\"action.id\"\r\n [attr.data-tone]=\"toneAttr(action)\"\r\n [attr.aria-disabled]=\"action.disabled ? 'true' : null\"\r\n [attr.aria-pressed]=\"pressedAttr(action)\"\r\n [attr.aria-haspopup]=\"action.menu ? 'menu' : null\"\r\n [attr.aria-expanded]=\"action.menu ? (openMenuActionId() === action.id ? 'true' : 'false') : null\"\r\n [attr.aria-label]=\"action.menu ? triggerAriaLabel(action) : resolvedLabel(action)\"\r\n [flyTooltip]=\"tooltipFor(action)\"\r\n [flyTooltipDelay]=\"450\"\r\n (click)=\"activate(action, $event)\"\r\n >\r\n <svg\r\n class=\"fma-btn__icon\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"1.8\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n aria-hidden=\"true\"\r\n >\r\n @for (node of iconNodes(action); track $index) {\r\n @switch (node.tag) {\r\n @case ('path') {\r\n <svg:path\r\n [attr.d]=\"node.attrs['d']\"\r\n [attr.fill-rule]=\"node.attrs['fill-rule']\"\r\n [attr.clip-rule]=\"node.attrs['clip-rule']\"\r\n />\r\n }\r\n @case ('circle') {\r\n <svg:circle\r\n [attr.cx]=\"node.attrs['cx']\"\r\n [attr.cy]=\"node.attrs['cy']\"\r\n [attr.r]=\"node.attrs['r']\"\r\n />\r\n }\r\n @case ('rect') {\r\n <svg:rect\r\n [attr.x]=\"node.attrs['x']\"\r\n [attr.y]=\"node.attrs['y']\"\r\n [attr.width]=\"node.attrs['width']\"\r\n [attr.height]=\"node.attrs['height']\"\r\n [attr.rx]=\"node.attrs['rx']\"\r\n [attr.ry]=\"node.attrs['ry']\"\r\n />\r\n }\r\n @case ('ellipse') {\r\n <svg:ellipse\r\n [attr.cx]=\"node.attrs['cx']\"\r\n [attr.cy]=\"node.attrs['cy']\"\r\n [attr.rx]=\"node.attrs['rx']\"\r\n [attr.ry]=\"node.attrs['ry']\"\r\n />\r\n }\r\n @case ('line') {\r\n <svg:line\r\n [attr.x1]=\"node.attrs['x1']\"\r\n [attr.y1]=\"node.attrs['y1']\"\r\n [attr.x2]=\"node.attrs['x2']\"\r\n [attr.y2]=\"node.attrs['y2']\"\r\n />\r\n }\r\n @case ('polyline') {\r\n <svg:polyline [attr.points]=\"node.attrs['points']\" />\r\n }\r\n @case ('polygon') {\r\n <svg:polygon [attr.points]=\"node.attrs['points']\" />\r\n }\r\n }\r\n }\r\n </svg>\r\n @if (hasBadge(action)) {\r\n <span class=\"fma-btn__badge\" aria-hidden=\"true\"></span>\r\n }\r\n </button>\r\n }\r\n</ng-template>\r\n\r\n<!--\r\n `.fma` is the SCROLLING row and holds the contributed groups only. The accent\r\n CTA is a SIBLING of it, outside the fade mask and outside the scroll container,\r\n because it is the row's anchor \u2014 a call to action that dissolves into a\r\n gradient, or that you have to scroll sideways to reach, inverts the affordance.\r\n It is also what makes `isOverflowing` measurable at all: see the component.\r\n-->\r\n@if (groups().length) {\r\n <div #row class=\"fma\" [class.fma--overflow]=\"isOverflowing()\">\r\n @for (group of groups(); track group.id) {\r\n <div\r\n class=\"fma-group\"\r\n [class.fma-group--framed]=\"group.framed\"\r\n role=\"group\"\r\n [attr.aria-label]=\"i18n.t(group.labelKey)\"\r\n >\r\n @for (action of group.items; track action.id) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"actionButton\"\r\n [ngTemplateOutletContext]=\"{ $implicit: action }\"\r\n />\r\n }\r\n </div>\r\n @if (group.framed) {\r\n <span class=\"fma-sep\" aria-hidden=\"true\"></span>\r\n }\r\n }\r\n </div>\r\n}\r\n\r\n<!--\r\n The accent CTA. The disc is a WRAPPER, not a modifier on the button, and that\r\n is load-bearing rather than cosmetic: `.fma-btn`'s hover/press feedback is an\r\n `animation` (`iconWiggle` / `iconBounce`), and an animation that touches\r\n `transform` beats a declared `transform` \u2014 so a hover lift authored on the\r\n button itself would simply never render. The wrapper owns the disc, the lift\r\n and the shadow; the button stays transparent and keeps the DS's own toolbar\r\n feedback. It re-points `--chrome-ink` rather than styling `.fma-btn` through a\r\n descendant selector so the glyph colour inherits the way custom properties do.\r\n-->\r\n@if (primary(); as p) {\r\n <span class=\"fma-primary\" [class.fma-primary--disabled]=\"p.disabled\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"actionButton\"\r\n [ngTemplateOutletContext]=\"{ $implicit: p }\"\r\n />\r\n </span>\r\n}\r\n\r\n<!--\r\n ONE radio menu, hoisted out of the group loops (S1-review F9). Only one is open\r\n at a time (`openMenuActionId`), so a single instance is enough \u2014 and rendering\r\n it beside its button would insert a non-`.fma-btn` element into the group,\r\n silently shifting every later button's `:nth-child` index and with it the\r\n `toolbarPopIn` stagger delay the `.fma-btn` rule assigns. Keeping every\r\n staggered parent's children homogeneous is what makes that rule's comment true.\r\n It portals itself to <body>, so its position is unaffected by where it is\r\n declared.\r\n-->\r\n@if (openAction(); as open) {\r\n <fly-context-menu\r\n [anchor]=\"menuAnchorEl()\"\r\n [sections]=\"menuSections(open)\"\r\n (action)=\"onMenuAction(open, $event)\"\r\n (closed)=\"closeMenu()\"\r\n />\r\n}\r\n", styles: [":host{display:flex;align-items:center;gap:8px;min-inline-size:0}.fma{display:flex;align-items:center;gap:8px;flex:0 1 auto;min-inline-size:0;overflow:hidden;flex-wrap:nowrap}.fma--overflow{overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none;-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent 100%);mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent 100%)}.fma--overflow::-webkit-scrollbar{display:none}:host([dir=rtl]) .fma--overflow{-webkit-mask-image:linear-gradient(to left,#000 calc(100% - 28px),transparent 100%);mask-image:linear-gradient(to left,#000 calc(100% - 28px),transparent 100%)}.fma-group{display:flex;align-items:center;gap:8px;flex:none}.fma-group--framed{gap:2px}.fma-sep{flex:0 0 auto;inline-size:1px;block-size:18px;background:var(--w14)}.fma-btn{position:relative;flex:0 0 auto;inline-size:30px;block-size:30px;display:grid;place-items:center;border:0;border-radius:50%;background:transparent;color:var(--chrome-ink);cursor:pointer;padding:0;animation:toolbarPopIn .3s var(--nova-ease-structural) both}.fma-btn:nth-child(1){animation-delay:0ms}.fma-btn:nth-child(2){animation-delay:40ms}.fma-btn:nth-child(3){animation-delay:80ms}.fma-btn:nth-child(4){animation-delay:.12s}.fma-btn:nth-child(5){animation-delay:.16s}.fma-btn:nth-child(6){animation-delay:.2s}.fma-btn:nth-child(7){animation-delay:.24s}.fma-btn:nth-child(8){animation-delay:.28s}.fma-btn:nth-child(9){animation-delay:.32s}.fma-btn:nth-child(10){animation-delay:.36s}.fma-btn:nth-child(11){animation-delay:.4s}.fma-btn:nth-child(12){animation-delay:.44s}.fma-btn:nth-child(n+13){animation-delay:.44s}.fma-btn:not([aria-disabled=true]):hover{background:var(--w1);color:var(--chrome-ink-hover);animation:iconWiggle .5s ease}.fma-btn:not([aria-disabled=true]):active{animation:iconBounce .38s var(--nova-ease-overshoot)}.fma-btn:focus-visible{outline:3px solid var(--accent);outline-offset:3px;border-radius:inherit}.fma-btn[data-tone=danger]{color:var(--sys-red)}.fma-btn[data-tone=danger]:not([aria-disabled=true]):hover{background:color-mix(in oklab,var(--sys-red) 18%,transparent);color:var(--sys-red)}.fma-btn[data-tone=success]{color:var(--sys-green)}.fma-btn[data-tone=success]:not([aria-disabled=true]):hover{background:color-mix(in oklab,var(--sys-green) 18%,transparent);color:var(--sys-green)}.fma-btn[aria-pressed=true]{background:var(--tint-sel);color:var(--chrome-ink-hover)}.fma-btn[aria-pressed=true][data-tone=success]{background:color-mix(in oklab,var(--sys-green) 22%,transparent);color:var(--sys-green)}.fma-btn[aria-disabled=true]{opacity:.38;filter:saturate(.6);animation:none;cursor:default}.fma-btn__icon{inline-size:16px;block-size:16px;pointer-events:none}.fma-btn__badge{position:absolute;inset-block-start:3px;inset-inline-end:3px;inline-size:7px;block-size:7px;border-radius:50%;background:var(--accent);pointer-events:none}.fma-primary{--chrome-ink: var(--on-accent);--chrome-ink-hover: var(--on-accent);flex:none;display:grid;place-items:center;inline-size:30px;block-size:30px;border-radius:50%;background:var(--accent);transition:box-shadow var(--nova-duration-hover) var(--nova-ease-micro),transform var(--nova-duration-hover) var(--nova-ease-micro)}.fma-primary:hover{box-shadow:0 8px 20px var(--tint-sel);transform:translateY(-1px)}.fma-primary--disabled{opacity:.38;filter:saturate(.6)}.fma-primary--disabled:hover{box-shadow:none;transform:none}.fma-btn--text{inline-size:auto;block-size:30px;border-radius:99px;padding-inline:12px;font-family:inherit;font-size:12.5px;font-weight:600;line-height:1;white-space:nowrap}@media(pointer:coarse){.fma-btn{inline-size:44px;block-size:44px}.fma-btn--text{inline-size:auto;min-inline-size:44px}.fma-primary{inline-size:44px;block-size:44px}}\n"] }]
|
|
17042
17359
|
}], ctorParameters: () => [], propDecorators: { groups: [{ type: i0.Input, args: [{ isSignal: true, alias: "groups", required: true }] }], primary: [{ type: i0.Input, args: [{ isSignal: true, alias: "primary", required: false }] }], rowRef: [{ type: i0.ViewChild, args: ['row', { isSignal: true }] }] } });
|
|
17043
17360
|
|
|
17044
17361
|
/**
|
|
@@ -19620,7 +19937,7 @@ class FlyCurrencySelectorComponent {
|
|
|
19620
19937
|
useExisting: forwardRef(() => FlyCurrencySelectorComponent),
|
|
19621
19938
|
multi: true,
|
|
19622
19939
|
},
|
|
19623
|
-
], 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\">🔒</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\">🔒</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 });
|
|
19624
19941
|
}
|
|
19625
19942
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyCurrencySelectorComponent, decorators: [{
|
|
19626
19943
|
type: Component,
|
|
@@ -21813,6 +22130,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
|
|
|
21813
22130
|
}]
|
|
21814
22131
|
}] });
|
|
21815
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;
|
|
21816
22143
|
/**
|
|
21817
22144
|
* Collapsible advanced-filter panel — structural port of the reference
|
|
21818
22145
|
* `.signals-page-fp` block (head row + optional status section + field area +
|
|
@@ -21837,6 +22164,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
|
|
|
21837
22164
|
* - Deferred pages set `showSearchButton` true, drop the per-field handlers,
|
|
21838
22165
|
* and commit staged criteria on `(search)`.
|
|
21839
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
|
+
*
|
|
21840
22179
|
* Matching the source, the panel renders nothing while `open` is false.
|
|
21841
22180
|
*/
|
|
21842
22181
|
class FlyFilterPanelComponent {
|
|
@@ -21855,18 +22194,66 @@ class FlyFilterPanelComponent {
|
|
|
21855
22194
|
* `[expandable]`.
|
|
21856
22195
|
*/
|
|
21857
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 */ []));
|
|
21858
22229
|
/** Clear-filters pressed — the page resets its own filter state. */
|
|
21859
22230
|
cleared = output();
|
|
21860
|
-
/** Close (×) pressed — the page flips its open flag. */
|
|
22231
|
+
/** Close (×) pressed, or a dismissing press outside — the page flips its open flag. */
|
|
21861
22232
|
closed = output();
|
|
21862
22233
|
/** Search pressed — deferred pages commit staged criteria and query. */
|
|
21863
22234
|
searched = output();
|
|
21864
22235
|
/** Whether the `[filter-more]` advanced fields are revealed. */
|
|
21865
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 */ []));
|
|
21866
22249
|
_host = inject(ElementRef);
|
|
22250
|
+
_doc = inject(DOCUMENT$1);
|
|
21867
22251
|
_destroyRef = inject(DestroyRef);
|
|
21868
22252
|
_clipObserver = null;
|
|
21869
22253
|
_placeRaf = 0;
|
|
22254
|
+
/** Marks where the host was declared, so un-portalling puts it back exactly. */
|
|
22255
|
+
_portalHome = null;
|
|
22256
|
+
_floatTeardown = null;
|
|
21870
22257
|
constructor() {
|
|
21871
22258
|
// Anchored placement: the CSS pins the panel's inline-end to the anchor
|
|
21872
22259
|
// wrapper, but the wrapper is usually a small search box that can sit
|
|
@@ -21877,10 +22264,22 @@ class FlyFilterPanelComponent {
|
|
|
21877
22264
|
// with a physical translate + width clamp so the panel always stays inside,
|
|
21878
22265
|
// whichever side of the window the anchor is on. Physical-pixel deltas are
|
|
21879
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.
|
|
21880
22270
|
afterRenderEffect(() => {
|
|
21881
|
-
const
|
|
22271
|
+
const floating = this.floating();
|
|
22272
|
+
const anchored = this.anchored() && this.open();
|
|
21882
22273
|
untracked(() => {
|
|
21883
|
-
if (
|
|
22274
|
+
if (floating) {
|
|
22275
|
+
this._teardownPlacement();
|
|
22276
|
+
this._portal();
|
|
22277
|
+
this._placeFloating();
|
|
22278
|
+
this._observeFloating();
|
|
22279
|
+
return;
|
|
22280
|
+
}
|
|
22281
|
+
this._teardownFloating();
|
|
22282
|
+
if (!anchored) {
|
|
21884
22283
|
this._teardownPlacement();
|
|
21885
22284
|
return;
|
|
21886
22285
|
}
|
|
@@ -21888,8 +22287,124 @@ class FlyFilterPanelComponent {
|
|
|
21888
22287
|
this._observeClipAncestor();
|
|
21889
22288
|
});
|
|
21890
22289
|
});
|
|
21891
|
-
this._destroyRef.onDestroy(() =>
|
|
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
|
+
};
|
|
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
|
+
}
|
|
21892
22406
|
}
|
|
22407
|
+
// ── Anchored (positioned-ancestor) placement ────────────────────────────────
|
|
21893
22408
|
_place() {
|
|
21894
22409
|
const host = this._host.nativeElement;
|
|
21895
22410
|
const win = host.ownerDocument.defaultView;
|
|
@@ -21900,14 +22415,13 @@ class FlyFilterPanelComponent {
|
|
|
21900
22415
|
host.style.removeProperty('--fp-max-w');
|
|
21901
22416
|
const clipRect = this._clipAncestor(host)?.getBoundingClientRect();
|
|
21902
22417
|
const bounds = clipRect ?? new DOMRect(0, 0, win.innerWidth, win.innerHeight);
|
|
21903
|
-
const
|
|
21904
|
-
const availableWidth = Math.max(240, bounds.width - 2 * pad);
|
|
22418
|
+
const availableWidth = Math.max(240, bounds.width - 2 * EDGE_PAD);
|
|
21905
22419
|
host.style.setProperty('--fp-max-w', `${Math.round(availableWidth)}px`);
|
|
21906
22420
|
// Setting the width clamp above forces layout before this read, so the rect
|
|
21907
22421
|
// already reflects the clamped size.
|
|
21908
22422
|
const rect = host.getBoundingClientRect();
|
|
21909
|
-
const minLeft = bounds.left +
|
|
21910
|
-
const maxRight = bounds.right -
|
|
22423
|
+
const minLeft = bounds.left + EDGE_PAD;
|
|
22424
|
+
const maxRight = bounds.right - EDGE_PAD;
|
|
21911
22425
|
let shift = 0;
|
|
21912
22426
|
if (rect.left < minLeft)
|
|
21913
22427
|
shift = minLeft - rect.left;
|
|
@@ -21950,12 +22464,15 @@ class FlyFilterPanelComponent {
|
|
|
21950
22464
|
host.style.removeProperty('--fp-max-w');
|
|
21951
22465
|
}
|
|
21952
22466
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyFilterPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
21953
|
-
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
|
|
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 });
|
|
21954
22468
|
}
|
|
21955
22469
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyFilterPanelComponent, decorators: [{
|
|
21956
22470
|
type: Component,
|
|
21957
|
-
args: [{ selector: 'fly-filter-panel', standalone: true, imports: [TranslatePipe, FlyButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
21958
|
-
|
|
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"] }] } });
|
|
21959
22476
|
|
|
21960
22477
|
/**
|
|
21961
22478
|
* Per-column cell override for {@link FlyDataTableComponent}. Declared on an
|
|
@@ -23001,7 +23518,7 @@ class FlyAppTopbarComponent {
|
|
|
23001
23518
|
this.rows()[index]?.nativeElement.focus();
|
|
23002
23519
|
}
|
|
23003
23520
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyAppTopbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
23004
|
-
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 });
|
|
23005
23522
|
}
|
|
23006
23523
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyAppTopbarComponent, decorators: [{
|
|
23007
23524
|
type: Component,
|
|
@@ -23651,13 +24168,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
|
|
|
23651
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 }] }] } });
|
|
23652
24169
|
|
|
23653
24170
|
/**
|
|
23654
|
-
* Segmented
|
|
23655
|
-
*
|
|
23656
|
-
*
|
|
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.
|
|
24174
|
+
*
|
|
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).
|
|
23657
24187
|
*
|
|
23658
|
-
* a11y:
|
|
23659
|
-
*
|
|
23660
|
-
*
|
|
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.
|
|
23661
24191
|
*/
|
|
23662
24192
|
class FlySegmentedComponent {
|
|
23663
24193
|
i18n = inject(I18nService);
|
|
@@ -23668,17 +24198,48 @@ class FlySegmentedComponent {
|
|
|
23668
24198
|
value = model(undefined, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
23669
24199
|
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
23670
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 */ []));
|
|
23671
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
|
+
}
|
|
23672
24224
|
tabIndexFor(index) {
|
|
23673
24225
|
const selected = this.selectedIndex();
|
|
23674
24226
|
return index === (selected >= 0 ? selected : 0) ? 0 : -1;
|
|
23675
24227
|
}
|
|
23676
24228
|
select(value) {
|
|
23677
|
-
if (
|
|
24229
|
+
if (this.disabled())
|
|
24230
|
+
return;
|
|
24231
|
+
if (!this.multiple()) {
|
|
23678
24232
|
this.value.set(value);
|
|
24233
|
+
return;
|
|
24234
|
+
}
|
|
24235
|
+
this.values.update(current => current.includes(value) ? current.filter(v => v !== value) : [...current, value]);
|
|
23679
24236
|
}
|
|
23680
24237
|
onKey(event) {
|
|
23681
|
-
|
|
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())
|
|
23682
24243
|
return;
|
|
23683
24244
|
const next = nextSegmentIndex(this.selectedIndex(), event.key, this.options().length, this.i18n.isRtl());
|
|
23684
24245
|
if (next === null)
|
|
@@ -23688,64 +24249,68 @@ class FlySegmentedComponent {
|
|
|
23688
24249
|
this.el.nativeElement.querySelectorAll('[role="radio"]')[next]?.focus();
|
|
23689
24250
|
}
|
|
23690
24251
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlySegmentedComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
23691
|
-
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: `
|
|
23692
|
-
<div
|
|
23693
|
-
class="seg"
|
|
23694
|
-
|
|
23695
|
-
|
|
23696
|
-
|
|
23697
|
-
|
|
23698
|
-
|
|
23699
|
-
|
|
23700
|
-
|
|
23701
|
-
|
|
23702
|
-
|
|
23703
|
-
|
|
23704
|
-
[
|
|
23705
|
-
[
|
|
23706
|
-
[attr.
|
|
23707
|
-
[
|
|
23708
|
-
|
|
23709
|
-
|
|
23710
|
-
|
|
23711
|
-
|
|
23712
|
-
|
|
23713
|
-
|
|
23714
|
-
|
|
23715
|
-
|
|
23716
|
-
|
|
23717
|
-
|
|
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 });
|
|
23718
24281
|
}
|
|
23719
24282
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlySegmentedComponent, decorators: [{
|
|
23720
24283
|
type: Component,
|
|
23721
|
-
args: [{ selector: 'fly-segmented', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
23722
|
-
<div
|
|
23723
|
-
class="seg"
|
|
23724
|
-
|
|
23725
|
-
|
|
23726
|
-
|
|
23727
|
-
|
|
23728
|
-
|
|
23729
|
-
|
|
23730
|
-
|
|
23731
|
-
|
|
23732
|
-
|
|
23733
|
-
|
|
23734
|
-
[
|
|
23735
|
-
[
|
|
23736
|
-
[attr.
|
|
23737
|
-
[
|
|
23738
|
-
|
|
23739
|
-
|
|
23740
|
-
|
|
23741
|
-
|
|
23742
|
-
|
|
23743
|
-
|
|
23744
|
-
|
|
23745
|
-
|
|
23746
|
-
|
|
23747
|
-
|
|
23748
|
-
|
|
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"] }] } });
|
|
23749
24314
|
|
|
23750
24315
|
/**
|
|
23751
24316
|
* Switch toggle (`role="switch"`). No legacy visual source existed — minimal
|
|
@@ -25502,5 +26067,5 @@ const AUDIENCE_ERROR_CODES = {
|
|
|
25502
26067
|
* Generated bundle index. Do not edit.
|
|
25503
26068
|
*/
|
|
25504
26069
|
|
|
25505
|
-
export { AGENT_DRAG_MIME, AGENT_PAYLOAD_VERSION, APP_LOOKUP, AUDIENCE_ERROR_CODES, AUDIENCE_LIMITS, AUDIENCE_PRESETS, AUDIENCE_TERM_KINDS, AgentActionBus, AgentActionUnsupportedDispatchError, AgentCommandRegistry, AgentDropRegistry, AgentFlightAnimator, AgentLookupRegistry, AgentPayloadOversizeError, AudienceBuilderComponent, AuthService, BilingualFieldComponent, CRON_MODES, CRON_WEEKDAYS, ContextMenuComponent, DEFAULT_AGENT_PAYLOAD_LIMITS, DEFAULT_FLY_THEME_MODE, DS_BASELINE_LOCALES, DashboardKpiComponent, DialogResult, ENTITY_LINK_LAUNCHER, EntityLookupComponent, FLYOS_LAUNCH_EVENT, FLYOS_LAUNCH_REQUEST_EVENT, FLYOS_REMOTE_ROUTE_EVENT, FLY_ACCENT_PROPERTY, FLY_ADMIN_ROLES, FLY_CHUNK_RELOAD_FLAG, FLY_COMPACT_THRESHOLD, FLY_COUNTRIES, FLY_CURRENCIES_BRIEF_ENDPOINT, FLY_EMOJI_BY_ID, FLY_EMOJI_CATEGORIES, FLY_EMOJI_CATEGORY_BY_ID, FLY_EMOJI_DEFAULT_CATEGORIES, FLY_EMOJI_PACK, FLY_EMOJI_PACK_MISSING_MESSAGE, FLY_EMOJI_PREVIEW_COUNT, FLY_EMPTY_VALUE, FLY_LOCALE_CATALOG, FLY_MAGIC_BAR_EVENT, FLY_MAGIC_BAR_ICONS, FLY_MAGIC_BAR_STORE_KEY, FLY_NF_CACHE_HEAL_FLAG, FLY_RELOAD_DEBOUNCE_MS, FLY_RELOAD_REARM_MS, FLY_REMOTE_BASE_PATH, FLY_REMOTE_CONTEXT_EVENT, FLY_REMOTE_CONTEXT_STORE_KEY, FLY_REMOTE_ROUTES, FLY_SCAN_PENDING_DEFAULT_DELAY_MS, FLY_SCAN_PENDING_MAX_RETRIES, FLY_SCAN_PENDING_STATUS, FLY_SEARCH_DEBOUNCE_MS, FLY_SKIN_TONES, FLY_STANDALONE_AUTH_CONFIG, FLY_THEME_MODE_IDS, FLY_VIEWPORT_IS_MOBILE, FLY_WINDOW_HELP_HINT_EVENT, FLY_WINDOW_PAGE_TITLE_EVENT, FlyAchievementProgressComponent, FlyActionMenuComponent, FlyActionStackComponent, FlyAgentDraggableDirective, FlyAnimatedEmojiComponent, FlyAppHomeComponent, FlyAppTopbarComponent, FlyAppUnavailableComponent, FlyAuthLiveRefresh, FlyBadgeWallComponent, FlyBlockUiComponent, FlyBreadcrumbComponent, FlyButtonComponent, FlyBytesPipe, FlyCaptchaComponent, FlyCardActionsComponent, FlyCardBodyComponent, FlyCardComponent, FlyCardFooterComponent, FlyCardGridComponent, FlyCardMediaComponent, FlyCardMetaComponent, FlyCardStatusComponent, FlyCardTitleComponent, FlyCellDirective, FlyCheckboxComponent, FlyChipComponent, FlyChunkReloadErrorHandler, FlyClickOutsideDirective, FlyCollapsibleComponent, FlyCommentThreadComponent, FlyCompactNumberPipe, FlyConfirmDialogComponent, FlyControlDirective, FlyCronBuilderComponent, FlyCurrencyCatalogService, FlyCurrencySelectorComponent, FlyDataTableComponent, FlyDatePipe, FlyDateTimePipe, FlyDebouncer, FlyDecimalNumberPipe, FlyDeepLinkPrefetchService, FlyDetailCardComponent, FlyDetailShellComponent, FlyDrawerComponent, FlyDurationPipe, FlyDynamicFormComponent, FlyEmojiPickerComponent, FlyFieldComponent, FlyFileDownloadService, FlyFileUploadComponent, FlyFilterPanelComponent, FlyFormBannerComponent, FlyFormFooterComponent, FlyFormGridComponent, FlyFormSectionComponent, FlyFullNumberPipe, FlyGanttComponent, FlyHubClient, FlyIconButtonComponent, FlyIfAdminDirective, FlyIfRoleDirective, FlyImageUploadComponent, FlyLeaderboardComponent, FlyMagicActionsComponent, FlyMetaListComponent, FlyModalComponent, FlyModerationQueueComponent, FlyModuleIconDirective, FlyMoneyPipe, FlyPaginationComponent, FlyPeoplePickerComponent, FlyPointsTierComponent, FlyProgressComponent, FlyRelativeTimePipe, FlyRemoteContextService, FlyRemoteRouter, FlyRemoteRouterOutletComponent, FlySearchInputComponent, FlySectionHeaderComponent, FlySecureSrcDirective, FlySegmentedComponent, FlySelectComponent, FlySkeletonComponent, FlySliderComponent, FlySparklineComponent, FlySpinnerComponent, FlyStandaloneAuthCallbackComponent, FlyStandaloneAuthService, FlyStandaloneMagicActionsComponent, FlyStateMessageComponent, FlyDynamicFormComponent as FlySurveyFormComponent, FlyTabComponent, FlyTabsComponent, FlyTagsInputComponent, FlyThemeService, FlyTimePipe, FlyToastService, FlyToggleComponent, FlyTooltipDirective, FlyTreeNavComponent, FlyTypeaheadComponent, FlyUserDirectoryService, FlyWindowHelpService, FlyWindowTitleService, FlyosPendingLaunchesGlobalKey, FlyosShellHandlesLaunchRequestsGlobalKey, FlyosShellOwnsHistoryGlobalKey, GANTT_ZOOMS, I18nService, LAUNCH_CONTEXT, MAGIC_BAR_SEARCH_WIDTH_DEFAULT, MAGIC_BAR_SEARCH_WIDTH_MAX, MAGIC_BAR_SEARCH_WIDTH_MIN, MagicBarRegistry, MessageBoxButtons, MessageBoxComponent, MessageBoxIcon, MessageBoxService, MockAuthService, NOVA_PRIORITY_TONE, NOVA_STATUS_TONE, NOVA_TONE_FALLBACK, OverlayStack, PRESENCE_COLORS, PageHeaderComponent, RTL_LOCALE_SET, SHARE_ORG_CHART_SYSTEM_KEY_APPS, SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT, SHARE_PANEL_DEFAULT_FILE_LEVELS, STATE_DEFAULT_ICONS, STATE_DEFAULT_MESSAGE_KEYS, STEP_UP_CODE, STEP_UP_REAUTH_HANDLER, STEP_UP_RETURN_KEY, SUPPORTED_AGENT_PAYLOAD_VERSIONS, SharePanelComponent, SourceAppResolver, StandaloneWindowManagerService, StatusBadgeComponent, StepUpService, ToastHostComponent, TranslatePipe, WINDOW_DATA, WINDOW_HELP_HINT, WindowManagerService, applySkinTone, applySuggestion, ariaSort, buildCron, canConfirm, canGoNext, canGoPrev, captureFocus, clampMagicBarSearchWidth, clampPage, clampSliderValue, connectRemoteLaunch, enterActivatesNatively, filterGroup, filterSuggestions, findLocaleByDialect, findLocaleByPrefix, firstEnabledIndex, firstModuleIndex, flattenModules, flyAdminGuard, flyApiErrorMessage, flyCaptchaBase64Utf8, flyCaptchaBuildToken, flyCaptchaSha256Hex, flyCompactNumber, flyDebounced, flyDecimalNumber, flyDownloadBlob, flyDuration, flyEmojiFor, flyEmojiIsTonable, flyEmojiPackUrl, flyExportFileName, flyFirstEnabledIndex, flyFormatBytes, flyFormatDate, flyFormatDateTime, flyFormatMoney, flyFormatTime, flyFullNumber, flyNextEnabledIndex, flyNextSegmentIndex, flyRelativeTime, flyResolveActiveTab, flyRoleGuard, flyScanPendingDelayMs, flyScanRetry, flySignedCompact, flySkinToneLabelKey, flyStandaloneAuthGuard, flyStandaloneAuthInterceptor, flyToDateOnly, flyToPage, flyUnwrap, flyUnwrapCurrencies, flyUnwrapLenient, hasAnyRoleIn, healNativeFederationCacheOnce, initialExpanded, isAccentColor, isChunkLoadError, isFlyAuthStorePopulated, isNativeFederationCacheError, isRtlLocale, isRtlLocaleEntry, isValidCron, isValidSingleField, loadRemoteStyles, magicBarOwnerKey, magicBarSearchText, matchFlyRoutePattern, nextEnabledIndex, nextModuleIndex, nextSegmentIndex, nextSort, normalizeFlyTheme, normalizeRoles, novaPriorityToneVar, novaStatusToneVar, overlayStack, parseCron, parseStepUpChallenge, parseTags, prefetchRemoteStyles, presenceColorFor, printConsoleSecurityWarning, provideFlyChunkReloadRecovery, provideFlyEmojiPack, provideFlyStandaloneAuth, reloadOnceForChunkError, requestAppLaunch, resolveActiveModule, resolveActiveSection, resolveActiveTab, resolveCellValue, resolveStateIcon, resolveStateMessageKey, restoreFocus, sameTags, sectionHintKey, sectionLayout, sliderTrackGradient, sparklinePath, sparklinePoints, stepUpInterceptor, stepUpReturnKey, trimAgentPayload, trimAgentString, unloadRemoteStyles, utf8ByteLength, validateAgentPayload, warnIfEmbeddedSessionMissing };
|
|
26070
|
+
export { AGENT_DRAG_MIME, AGENT_PAYLOAD_VERSION, APP_LOOKUP, AUDIENCE_ERROR_CODES, AUDIENCE_LIMITS, AUDIENCE_PRESETS, AUDIENCE_TERM_KINDS, AgentActionBus, AgentActionUnsupportedDispatchError, AgentCommandRegistry, AgentDropRegistry, AgentFlightAnimator, AgentLookupRegistry, AgentPayloadOversizeError, AudienceBuilderComponent, AuthService, BilingualFieldComponent, CRON_MODES, CRON_WEEKDAYS, ContextMenuComponent, DEFAULT_AGENT_PAYLOAD_LIMITS, DEFAULT_FLY_THEME_MODE, DS_BASELINE_LOCALES, DashboardKpiComponent, DialogResult, ENTITY_LINK_LAUNCHER, EntityLookupComponent, FLYOS_LAUNCH_EVENT, FLYOS_LAUNCH_REQUEST_EVENT, FLYOS_REMOTE_ROUTE_EVENT, FLY_ACCENT_PROPERTY, FLY_ADMIN_ROLES, FLY_CHUNK_RELOAD_FLAG, FLY_COMPACT_THRESHOLD, FLY_COUNTRIES, FLY_CURRENCIES_BRIEF_ENDPOINT, FLY_EMOJI_BY_ID, FLY_EMOJI_CATEGORIES, FLY_EMOJI_CATEGORY_BY_ID, FLY_EMOJI_DEFAULT_CATEGORIES, FLY_EMOJI_PACK, FLY_EMOJI_PACK_MISSING_MESSAGE, FLY_EMOJI_PREVIEW_COUNT, FLY_EMPTY_VALUE, FLY_LOCALE_CATALOG, FLY_MAGIC_BAR_EVENT, FLY_MAGIC_BAR_ICONS, FLY_MAGIC_BAR_STORE_KEY, FLY_NF_CACHE_HEAL_FLAG, FLY_RELOAD_DEBOUNCE_MS, FLY_RELOAD_REARM_MS, FLY_REMOTE_BASE_PATH, FLY_REMOTE_CONTEXT_EVENT, FLY_REMOTE_CONTEXT_STORE_KEY, FLY_REMOTE_ROUTES, FLY_SCAN_PENDING_DEFAULT_DELAY_MS, FLY_SCAN_PENDING_MAX_RETRIES, FLY_SCAN_PENDING_STATUS, FLY_SEARCH_DEBOUNCE_MS, FLY_SKIN_TONES, FLY_STANDALONE_AUTH_CONFIG, FLY_THEME_MODE_IDS, FLY_VIEWPORT_IS_MOBILE, FLY_WINDOW_HELP_HINT_EVENT, FLY_WINDOW_PAGE_TITLE_EVENT, FlyAchievementProgressComponent, FlyActionMenuComponent, FlyActionStackComponent, FlyAgentDraggableDirective, FlyAnimatedEmojiComponent, FlyAppHomeComponent, FlyAppTopbarComponent, FlyAppUnavailableComponent, FlyAuthLiveRefresh, FlyBadgeWallComponent, FlyBlockUiComponent, FlyBreadcrumbComponent, FlyButtonComponent, FlyBytesPipe, FlyCaptchaComponent, FlyCardActionsComponent, FlyCardBodyComponent, FlyCardComponent, FlyCardFooterComponent, FlyCardGridComponent, FlyCardMediaComponent, FlyCardMetaComponent, FlyCardStatusComponent, FlyCardTitleComponent, FlyCellDirective, FlyCheckboxComponent, FlyChipComponent, FlyChunkReloadErrorHandler, FlyClickOutsideDirective, FlyCollapsibleComponent, FlyCommentThreadComponent, FlyCompactNumberPipe, FlyConfirmDialogComponent, FlyControlDirective, FlyCronBuilderComponent, FlyCurrencyCatalogService, FlyCurrencySelectorComponent, FlyDataTableComponent, FlyDatePipe, FlyDateTimePipe, FlyDebouncer, FlyDecimalNumberPipe, FlyDeepLinkPrefetchService, FlyDetailCardComponent, FlyDetailShellComponent, FlyDrawerComponent, FlyDurationPipe, FlyDynamicFormComponent, FlyEmojiPickerComponent, FlyFieldComponent, FlyFileDownloadService, FlyFileUploadComponent, FlyFilterPanelComponent, FlyFormBannerComponent, FlyFormFooterComponent, FlyFormGridComponent, FlyFormSectionComponent, FlyFullNumberPipe, FlyGanttComponent, FlyHubClient, FlyIconButtonComponent, FlyIfAdminDirective, FlyIfRoleDirective, FlyImageUploadComponent, FlyLeaderboardComponent, FlyMagicActionsComponent, FlyMetaListComponent, FlyModalComponent, FlyModerationQueueComponent, FlyModuleIconDirective, FlyMoneyPipe, FlyPaginationComponent, FlyPeoplePickerComponent, FlyPointsTierComponent, FlyProgressComponent, FlyRelativeTimePipe, FlyRemoteContextService, FlyRemoteRouter, FlyRemoteRouterOutletComponent, FlySearchInputComponent, FlySectionHeaderComponent, FlySecureSrcDirective, FlySegmentedComponent, FlySelectComponent, FlySkeletonComponent, FlySliderComponent, FlySparklineComponent, FlySpinnerComponent, FlyStandaloneAuthCallbackComponent, FlyStandaloneAuthService, FlyStandaloneMagicActionsComponent, FlyStateMessageComponent, FlyDynamicFormComponent as FlySurveyFormComponent, FlyTabComponent, FlyTabsComponent, FlyTagsInputComponent, FlyThemeService, FlyTimePipe, FlyToastService, FlyToggleComponent, FlyTooltipDirective, FlyTreeNavComponent, FlyTypeaheadComponent, FlyUserDirectoryService, FlyWindowHelpService, FlyWindowTitleService, FlyosPendingLaunchesGlobalKey, FlyosShellHandlesLaunchRequestsGlobalKey, FlyosShellOwnsHistoryGlobalKey, GANTT_DEPENDENCY_TYPES, GANTT_ROW_TINT_ALPHA, GANTT_ZOOMS, I18nService, LAUNCH_CONTEXT, MAGIC_BAR_SEARCH_WIDTH_DEFAULT, MAGIC_BAR_SEARCH_WIDTH_MAX, MAGIC_BAR_SEARCH_WIDTH_MIN, MagicBarRegistry, MessageBoxButtons, MessageBoxComponent, MessageBoxIcon, MessageBoxService, MockAuthService, NOVA_PRIORITY_TONE, NOVA_STATUS_TONE, NOVA_TONE_FALLBACK, OverlayStack, PRESENCE_COLORS, PageHeaderComponent, RTL_LOCALE_SET, SHARE_ORG_CHART_SYSTEM_KEY_APPS, SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT, SHARE_PANEL_DEFAULT_FILE_LEVELS, STATE_DEFAULT_ICONS, STATE_DEFAULT_MESSAGE_KEYS, STEP_UP_CODE, STEP_UP_REAUTH_HANDLER, STEP_UP_RETURN_KEY, SUPPORTED_AGENT_PAYLOAD_VERSIONS, SharePanelComponent, SourceAppResolver, StandaloneWindowManagerService, StatusBadgeComponent, StepUpService, ToastHostComponent, TranslatePipe, WINDOW_DATA, WINDOW_HELP_HINT, WindowManagerService, applySkinTone, applySuggestion, ariaSort, buildCron, canConfirm, canGoNext, canGoPrev, captureFocus, clampMagicBarSearchWidth, clampPage, clampSliderValue, connectRemoteLaunch, enterActivatesNatively, filterGroup, filterSuggestions, findLocaleByDialect, findLocaleByPrefix, firstEnabledIndex, firstModuleIndex, flattenModules, flyAdminGuard, flyApiErrorMessage, flyCaptchaBase64Utf8, flyCaptchaBuildToken, flyCaptchaSha256Hex, flyCompactNumber, flyDebounced, flyDecimalNumber, flyDownloadBlob, flyDuration, flyEmojiFor, flyEmojiIsTonable, flyEmojiPackUrl, flyExportFileName, flyFirstEnabledIndex, flyFormatBytes, flyFormatDate, flyFormatDateTime, flyFormatMoney, flyFormatTime, flyFullNumber, flyNextEnabledIndex, flyNextSegmentIndex, flyRelativeTime, flyResolveActiveTab, flyRoleGuard, flyScanPendingDelayMs, flyScanRetry, flySignedCompact, flySkinToneLabelKey, flyStandaloneAuthGuard, flyStandaloneAuthInterceptor, flyToDateOnly, flyToPage, flyUnwrap, flyUnwrapCurrencies, flyUnwrapLenient, hasAnyRoleIn, healNativeFederationCacheOnce, initialExpanded, isAccentColor, isChunkLoadError, isFlyAuthStorePopulated, isNativeFederationCacheError, isRtlLocale, isRtlLocaleEntry, isValidCron, isValidSingleField, loadRemoteStyles, magicBarOwnerKey, magicBarSearchText, matchFlyRoutePattern, nextEnabledIndex, nextModuleIndex, nextSegmentIndex, nextSort, normalizeFlyTheme, normalizeRoles, novaPriorityToneVar, novaStatusToneVar, overlayStack, parseCron, parseStepUpChallenge, parseTags, prefetchRemoteStyles, presenceColorFor, printConsoleSecurityWarning, provideFlyChunkReloadRecovery, provideFlyEmojiPack, provideFlyStandaloneAuth, reloadOnceForChunkError, requestAppLaunch, resolveActiveModule, resolveActiveSection, resolveActiveTab, resolveCellValue, resolveStateIcon, resolveStateMessageKey, restoreFocus, sameTags, sectionHintKey, sectionLayout, sliderTrackGradient, sparklinePath, sparklinePoints, stepUpInterceptor, stepUpReturnKey, trimAgentPayload, trimAgentString, unloadRemoteStyles, utf8ByteLength, validateAgentPayload, warnIfEmbeddedSessionMissing };
|
|
25506
26071
|
//# sourceMappingURL=flyos-design-system.mjs.map
|