@flyos/design-system 3.2.0 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/flyos-design-system.mjs +1022 -291
- package/fesm2022/flyos-design-system.mjs.map +1 -1
- package/package.json +1 -1
- package/scss/_fly-theme.scss +5 -0
- package/scss/_nova-tokens.scss +22 -0
- package/scss/_nova-type.scss +51 -12
- package/scss/_sf-arabic-font.scss +32 -0
- package/scss/_tokens.scss +56 -20
- package/types/flyos-design-system.d.ts +458 -75
- package/types/flyos-design-system.d.ts.map +1 -1
|
@@ -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.4.0';
|
|
51
51
|
const FLY_DS_REGISTRY_KEY = '__FLY_DS_INSTANCES__';
|
|
52
52
|
/**
|
|
53
53
|
* Records this design-system instance on the shared `scope` and returns the
|
|
@@ -1680,6 +1680,9 @@ const DS_BASELINE_LOCALES = {
|
|
|
1680
1680
|
'ui.nav.selectModule': 'Select a module',
|
|
1681
1681
|
'ui.nav.sections': 'Sections',
|
|
1682
1682
|
'ui.nav.switchModule': 'Switch module',
|
|
1683
|
+
'ui.pager.next': 'Next page',
|
|
1684
|
+
'ui.pager.page': 'Page {{page}}',
|
|
1685
|
+
'ui.pager.previous': 'Previous page',
|
|
1683
1686
|
'ui.pagination.first': 'First page',
|
|
1684
1687
|
'ui.pagination.label': 'Pagination',
|
|
1685
1688
|
'ui.pagination.last': 'Last page',
|
|
@@ -2070,6 +2073,9 @@ const DS_BASELINE_LOCALES = {
|
|
|
2070
2073
|
'ui.nav.selectModule': 'اختر وحدة',
|
|
2071
2074
|
'ui.nav.sections': 'الأقسام',
|
|
2072
2075
|
'ui.nav.switchModule': 'تبديل الوحدة',
|
|
2076
|
+
'ui.pager.next': 'الصفحة التالية',
|
|
2077
|
+
'ui.pager.page': 'الصفحة {{page}}',
|
|
2078
|
+
'ui.pager.previous': 'الصفحة السابقة',
|
|
2073
2079
|
'ui.pagination.first': 'الصفحة الأولى',
|
|
2074
2080
|
'ui.pagination.label': 'تقسيم الصفحات',
|
|
2075
2081
|
'ui.pagination.last': 'الصفحة الأخيرة',
|
|
@@ -2460,6 +2466,9 @@ const DS_BASELINE_LOCALES = {
|
|
|
2460
2466
|
'ui.nav.selectModule': 'Choisir un module',
|
|
2461
2467
|
'ui.nav.sections': 'Sections',
|
|
2462
2468
|
'ui.nav.switchModule': 'Changer de module',
|
|
2469
|
+
'ui.pager.next': 'Page suivante',
|
|
2470
|
+
'ui.pager.page': 'Page {{page}}',
|
|
2471
|
+
'ui.pager.previous': 'Page précédente',
|
|
2463
2472
|
'ui.pagination.first': 'Première page',
|
|
2464
2473
|
'ui.pagination.label': 'Pagination',
|
|
2465
2474
|
'ui.pagination.last': 'Dernière page',
|
|
@@ -2849,6 +2858,9 @@ const DS_BASELINE_LOCALES = {
|
|
|
2849
2858
|
'ui.nav.selectModule': 'ماڈیول منتخب کریں',
|
|
2850
2859
|
'ui.nav.sections': 'حصے',
|
|
2851
2860
|
'ui.nav.switchModule': 'ماڈیول تبدیل کریں',
|
|
2861
|
+
'ui.pager.next': 'اگلا صفحہ',
|
|
2862
|
+
'ui.pager.page': 'صفحہ {{page}}',
|
|
2863
|
+
'ui.pager.previous': 'پچھلا صفحہ',
|
|
2852
2864
|
'ui.pagination.first': 'پہلا صفحہ',
|
|
2853
2865
|
'ui.pagination.label': 'صفحہ بندی',
|
|
2854
2866
|
'ui.pagination.last': 'آخری صفحہ',
|
|
@@ -4008,12 +4020,12 @@ function connectRemoteLaunch(opts) {
|
|
|
4008
4020
|
const FLY_REMOTE_CONTEXT_STORE_KEY = '__flyRemoteContext__';
|
|
4009
4021
|
const FLY_REMOTE_CONTEXT_EVENT = 'fly:remote-context';
|
|
4010
4022
|
/** The single cross-bundle store, lazily created on `globalThis`. */
|
|
4011
|
-
function store$
|
|
4023
|
+
function store$2() {
|
|
4012
4024
|
const g = globalThis;
|
|
4013
4025
|
return (g[FLY_REMOTE_CONTEXT_STORE_KEY] ??= {});
|
|
4014
4026
|
}
|
|
4015
4027
|
/** Notify every federated copy of the service that the store changed. */
|
|
4016
|
-
function emitSync$
|
|
4028
|
+
function emitSync$2() {
|
|
4017
4029
|
const g = globalThis;
|
|
4018
4030
|
try {
|
|
4019
4031
|
g.dispatchEvent?.(new Event(FLY_REMOTE_CONTEXT_EVENT));
|
|
@@ -4070,33 +4082,33 @@ function toMap(s) {
|
|
|
4070
4082
|
* dispatch is ever needed.
|
|
4071
4083
|
*/
|
|
4072
4084
|
class FlyRemoteContextService {
|
|
4073
|
-
_byApp = signal(toMap(store$
|
|
4085
|
+
_byApp = signal(toMap(store$2()), ...(ngDevMode ? [{ debugName: "_byApp" }] : /* istanbul ignore next */ []));
|
|
4074
4086
|
/** All currently-published contexts, keyed by app id. Reactive. */
|
|
4075
4087
|
contexts = this._byApp.asReadonly();
|
|
4076
4088
|
constructor() {
|
|
4077
4089
|
// Keep this instance's reactive mirror current when ANOTHER federated copy of
|
|
4078
4090
|
// the service — or a remote writing the store slot directly — mutates it.
|
|
4079
4091
|
const g = globalThis;
|
|
4080
|
-
g.addEventListener?.(FLY_REMOTE_CONTEXT_EVENT, () => this._byApp.set(toMap(store$
|
|
4092
|
+
g.addEventListener?.(FLY_REMOTE_CONTEXT_EVENT, () => this._byApp.set(toMap(store$2())));
|
|
4081
4093
|
}
|
|
4082
4094
|
/** Publish (or replace) the active route context for an app. */
|
|
4083
4095
|
publish(ctx) {
|
|
4084
|
-
store$
|
|
4085
|
-
this._byApp.set(toMap(store$
|
|
4086
|
-
emitSync$
|
|
4096
|
+
store$2()[ctx.appId] = ctx;
|
|
4097
|
+
this._byApp.set(toMap(store$2()));
|
|
4098
|
+
emitSync$2();
|
|
4087
4099
|
}
|
|
4088
4100
|
/** Drop an app's context (remote unmounted / window closed). No-op if absent. */
|
|
4089
4101
|
clear(appId) {
|
|
4090
|
-
const s = store$
|
|
4102
|
+
const s = store$2();
|
|
4091
4103
|
if (!(appId in s))
|
|
4092
4104
|
return;
|
|
4093
4105
|
delete s[appId];
|
|
4094
4106
|
this._byApp.set(toMap(s));
|
|
4095
|
-
emitSync$
|
|
4107
|
+
emitSync$2();
|
|
4096
4108
|
}
|
|
4097
4109
|
/** Current context for an app, or `null` when nothing is published. */
|
|
4098
4110
|
context(appId) {
|
|
4099
|
-
return store$
|
|
4111
|
+
return store$2()[appId] ?? null;
|
|
4100
4112
|
}
|
|
4101
4113
|
/**
|
|
4102
4114
|
* Resolve a single route param for an app, e.g. `param('circles', 'id')`.
|
|
@@ -4104,7 +4116,7 @@ class FlyRemoteContextService {
|
|
|
4104
4116
|
* the caller then degrades (the skill asks for the missing id).
|
|
4105
4117
|
*/
|
|
4106
4118
|
param(appId, path) {
|
|
4107
|
-
const value = store$
|
|
4119
|
+
const value = store$2()[appId]?.params[path];
|
|
4108
4120
|
return value != null && value !== '' ? value : null;
|
|
4109
4121
|
}
|
|
4110
4122
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyRemoteContextService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
@@ -6177,6 +6189,24 @@ class FlyClickOutsideDirective {
|
|
|
6177
6189
|
* Defaults to always-on for an always-mounted consumer.
|
|
6178
6190
|
*/
|
|
6179
6191
|
enabled = input(true, { ...(ngDevMode ? { debugName: "enabled" } : /* istanbul ignore next */ {}), alias: 'flyClickOutsideEnabled' });
|
|
6192
|
+
/**
|
|
6193
|
+
* Extra element(s) that count as INSIDE even though they are not in the host —
|
|
6194
|
+
* the escape hatch for a popover whose TRIGGER cannot be wrapped by the host.
|
|
6195
|
+
*
|
|
6196
|
+
* The docblock's canonical shape puts the host around trigger and panel both, and
|
|
6197
|
+
* that is still the right answer whenever it is available. It is not always: a
|
|
6198
|
+
* panel anchored to a control in the desktop shell's chrome (a magic-bar filter
|
|
6199
|
+
* toggle) has its trigger in another component, another stacking context and
|
|
6200
|
+
* another bundle. Left unlisted, the trigger reads as outside, and because this
|
|
6201
|
+
* directive fires on `pointerdown` — strictly BEFORE the trigger's own `click` —
|
|
6202
|
+
* pressing an open panel's own toggle dismisses it and the toggle then reopens it.
|
|
6203
|
+
* The panel appears not to close at all, which reads as this directive being
|
|
6204
|
+
* broken rather than as an ordering problem.
|
|
6205
|
+
*
|
|
6206
|
+
* A single element or an array; `null`/empty (the default) keeps the host-only
|
|
6207
|
+
* behaviour byte-for-byte.
|
|
6208
|
+
*/
|
|
6209
|
+
ignore = input(null, { ...(ngDevMode ? { debugName: "ignore" } : /* istanbul ignore next */ {}), alias: 'flyClickOutsideIgnore' });
|
|
6180
6210
|
/** Emitted once when a pointer press lands outside the host while enabled. */
|
|
6181
6211
|
flyClickOutside = output();
|
|
6182
6212
|
// `pointerdown` (not `click`): fires at the very start of the gesture — before focus
|
|
@@ -6192,14 +6222,18 @@ class FlyClickOutsideDirective {
|
|
|
6192
6222
|
// even if the target detaches mid-handler (a menu item that removes its own row),
|
|
6193
6223
|
// and it pierces shadow roots. Fall back to `contains` for any UA without it.
|
|
6194
6224
|
const path = event.composedPath?.();
|
|
6195
|
-
const
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6225
|
+
const target = event.target;
|
|
6226
|
+
const within = (el) => !!el && (path && path.length ? path.includes(el) : el.contains(target));
|
|
6227
|
+
if (within(host))
|
|
6228
|
+
return;
|
|
6229
|
+
const ignore = this.ignore();
|
|
6230
|
+
const extras = ignore === null ? [] : Array.isArray(ignore) ? ignore : [ignore];
|
|
6231
|
+
if (extras.some(within))
|
|
6232
|
+
return;
|
|
6233
|
+
this.flyClickOutside.emit();
|
|
6200
6234
|
}
|
|
6201
6235
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyClickOutsideDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
6202
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.19", type: FlyClickOutsideDirective, isStandalone: true, selector: "[flyClickOutside]", inputs: { enabled: { classPropertyName: "enabled", publicName: "flyClickOutsideEnabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { flyClickOutside: "flyClickOutside" }, host: { listeners: { "document:pointerdown": "onDocumentPointerDown($event)" } }, ngImport: i0 });
|
|
6236
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.19", type: FlyClickOutsideDirective, isStandalone: true, selector: "[flyClickOutside]", inputs: { enabled: { classPropertyName: "enabled", publicName: "flyClickOutsideEnabled", isSignal: true, isRequired: false, transformFunction: null }, ignore: { classPropertyName: "ignore", publicName: "flyClickOutsideIgnore", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { flyClickOutside: "flyClickOutside" }, host: { listeners: { "document:pointerdown": "onDocumentPointerDown($event)" } }, ngImport: i0 });
|
|
6203
6237
|
}
|
|
6204
6238
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyClickOutsideDirective, decorators: [{
|
|
6205
6239
|
type: Directive,
|
|
@@ -6207,7 +6241,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
|
|
|
6207
6241
|
selector: '[flyClickOutside]',
|
|
6208
6242
|
standalone: true,
|
|
6209
6243
|
}]
|
|
6210
|
-
}], propDecorators: { enabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "flyClickOutsideEnabled", required: false }] }], flyClickOutside: [{ type: i0.Output, args: ["flyClickOutside"] }], onDocumentPointerDown: [{
|
|
6244
|
+
}], propDecorators: { enabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "flyClickOutsideEnabled", required: false }] }], ignore: [{ type: i0.Input, args: [{ isSignal: true, alias: "flyClickOutsideIgnore", required: false }] }], flyClickOutside: [{ type: i0.Output, args: ["flyClickOutside"] }], onDocumentPointerDown: [{
|
|
6211
6245
|
type: HostListener,
|
|
6212
6246
|
args: ['document:pointerdown', ['$event']]
|
|
6213
6247
|
}] } });
|
|
@@ -7857,7 +7891,7 @@ class FlyEmojiPickerComponent {
|
|
|
7857
7891
|
this.selectionChange.emit(next);
|
|
7858
7892
|
}
|
|
7859
7893
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyEmojiPickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
7860
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyEmojiPickerComponent, isStandalone: true, selector: "fly-emoji-picker", inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, selectionMode: { classPropertyName: "selectionMode", publicName: "selectionMode", isSignal: true, isRequired: false, transformFunction: null }, selectedIds: { classPropertyName: "selectedIds", publicName: "selectedIds", isSignal: true, isRequired: false, transformFunction: null }, categories: { classPropertyName: "categories", publicName: "categories", isSignal: true, isRequired: false, transformFunction: null }, defaultCategory: { classPropertyName: "defaultCategory", publicName: "defaultCategory", isSignal: true, isRequired: false, transformFunction: null }, skinTones: { classPropertyName: "skinTones", publicName: "skinTones", isSignal: true, isRequired: false, transformFunction: null }, skinTone: { classPropertyName: "skinTone", publicName: "skinTone", isSignal: true, isRequired: false, transformFunction: null }, showMore: { classPropertyName: "showMore", publicName: "showMore", isSignal: true, isRequired: false, transformFunction: null }, previewCount: { classPropertyName: "previewCount", publicName: "previewCount", isSignal: true, isRequired: false, transformFunction: null }, emojiAnimation: { classPropertyName: "emojiAnimation", publicName: "emojiAnimation", isSignal: true, isRequired: false, transformFunction: null }, dismissOnOutsidePress: { classPropertyName: "dismissOnOutsidePress", publicName: "dismissOnOutsidePress", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { stickerPicked: "stickerPicked", toggled: "toggled", selectionChange: "selectionChange", skinToneChange: "skinToneChange", dismissed: "dismissed" }, host: { properties: { "class.fly-emoji-picker--small": "size() === 'small'", "class.fly-emoji-picker--medium": "size() === 'medium'", "class.fly-emoji-picker--large": "size() === 'large'" } }, viewQueries: [{ propertyName: "searchRef", first: true, predicate: ["searchRef"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\r\n class=\"fly-emoji-picker\"\r\n [flyClickOutsideEnabled]=\"dismissOnOutsidePress()\"\r\n (flyClickOutside)=\"dismissed.emit()\"\r\n [attr.aria-label]=\"'common.label.emoji_picker' | translate\">\r\n <!-- Stickers mode only: the emoji grid is unchanged, stickers are an ADDITIONAL surface. -->\r\n @if (showTabs()) {\r\n <div\r\n class=\"fly-emoji-picker__tabs\"\r\n role=\"tablist\"\r\n [attr.aria-label]=\"'common.label.emoji_tabs' | translate\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__tab\"\r\n role=\"tab\"\r\n id=\"fly-emoji-tab-emoji\"\r\n aria-controls=\"fly-emoji-panel-emoji\"\r\n [class.is-active]=\"activeTab() === 'emoji'\"\r\n [attr.aria-selected]=\"activeTab() === 'emoji'\"\r\n [attr.tabindex]=\"activeTab() === 'emoji' ? 0 : -1\"\r\n (keydown)=\"onTabKeydown($event)\"\r\n (click)=\"selectTab('emoji')\">\r\n {{ 'common.label.emoji_tab_emoji' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__tab\"\r\n role=\"tab\"\r\n id=\"fly-emoji-tab-stickers\"\r\n aria-controls=\"fly-emoji-panel-stickers\"\r\n [class.is-active]=\"activeTab() === 'stickers'\"\r\n [attr.aria-selected]=\"activeTab() === 'stickers'\"\r\n [attr.tabindex]=\"activeTab() === 'stickers' ? 0 : -1\"\r\n (keydown)=\"onTabKeydown($event)\"\r\n (click)=\"selectTab('stickers')\">\r\n {{ 'common.label.emoji_tab_stickers' | translate }}\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (showTabs() && activeTab() === 'stickers') {\r\n <div\r\n class=\"fly-emoji-picker__stickers\"\r\n id=\"fly-emoji-panel-stickers\"\r\n role=\"tabpanel\"\r\n aria-labelledby=\"fly-emoji-tab-stickers\">\r\n @for (entry of stickers(); track entry.id) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__sticker\"\r\n [flyTooltip]=\"entry.label\"\r\n [attr.aria-label]=\"entry.label\"\r\n (pointerenter)=\"onCellHover(sticker)\"\r\n (pointerleave)=\"cancelHover()\"\r\n (click)=\"pickSticker(entry)\">\r\n <fly-animated-emoji #sticker [id]=\"entry.id\" [size]=\"64\" />\r\n </button>\r\n } @empty {\r\n <p class=\"fly-emoji-picker__empty\">{{ 'agent.lookup.no_results' | translate }}</p>\r\n }\r\n </div>\r\n }\r\n\r\n @if (!showTabs() || activeTab() === 'emoji') {\r\n <!-- Search + tone sit together as one chrome bar: the tone swatches are hands, and on their own row\r\n above the grid they read as pickable content rather than a setting. -->\r\n <div class=\"fly-emoji-picker__bar\">\r\n <input\r\n class=\"fly-emoji-picker__search\"\r\n type=\"text\"\r\n #searchRef\r\n [value]=\"query()\"\r\n (input)=\"onQueryInput(searchRef.value)\"\r\n [placeholder]=\"'common.label.search_emoji' | translate\"\r\n [attr.aria-label]=\"'common.label.search_emoji' | translate\"\r\n spellcheck=\"false\"\r\n autocomplete=\"off\" />\r\n\r\n @if (showToneStrip()) {\r\n <!-- Escape-to-close listener only \u2014 every actual control inside (trigger + options) is its\r\n own focusable button, so this wrapper itself doesn't need to be. -->\r\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\r\n <div\r\n class=\"fly-emoji-picker__tones\"\r\n [flyClickOutsideEnabled]=\"toneMenuOpen()\"\r\n (flyClickOutside)=\"toneMenuOpen.set(false)\"\r\n (keydown.escape)=\"toneMenuOpen.set(false)\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__tone-trigger\"\r\n aria-haspopup=\"listbox\"\r\n [attr.aria-expanded]=\"toneMenuOpen()\"\r\n [attr.aria-label]=\"'common.label.skin_tone' | translate\"\r\n [flyTooltip]=\"toneLabelKey(effectiveTone()) | translate\"\r\n (click)=\"toneMenuOpen.set(!toneMenuOpen())\">\r\n <span class=\"fly-emoji-picker__tone-glyph\">{{ toneSwatch(effectiveTone()) }}</span>\r\n <i class=\"pi pi-chevron-down fly-emoji-picker__tone-caret\" aria-hidden=\"true\"></i>\r\n </button>\r\n\r\n @if (toneMenuOpen()) {\r\n <div\r\n class=\"fly-emoji-picker__tone-menu\"\r\n role=\"listbox\"\r\n [attr.aria-label]=\"'common.label.skin_tone' | translate\">\r\n @for (tone of tones; track tone) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__tone-option\"\r\n role=\"option\"\r\n [class.is-active]=\"effectiveTone() === tone\"\r\n [attr.aria-selected]=\"effectiveTone() === tone\"\r\n [flyTooltip]=\"toneLabelKey(tone) | translate\"\r\n (click)=\"onToneOptionClick(tone)\">\r\n {{ toneSwatch(tone) }}\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- ONE scrolling list; each category is a separator label over its own grid. Tabs wrapped to three\r\n rows in a sidebar drawer and cost a click to reach any group. -->\r\n <div\r\n class=\"fly-emoji-picker__list\"\r\n role=\"listbox\"\r\n [attr.id]=\"showTabs() ? 'fly-emoji-panel-emoji' : null\"\r\n [attr.aria-labelledby]=\"showTabs() ? 'fly-emoji-tab-emoji' : null\"\r\n [attr.aria-multiselectable]=\"selectionMode() === 'multi'\">\r\n @for (section of sections(); track section.id) {\r\n <div class=\"fly-emoji-picker__section\" role=\"group\" [attr.aria-label]=\"sectionLabel(section)\">\r\n <h4 class=\"fly-emoji-picker__heading\">{{ sectionLabel(section) }}</h4>\r\n <div class=\"fly-emoji-picker__grid\">\r\n @for (cell of section.cells; track cell.entry.id) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__cell\"\r\n role=\"option\"\r\n [class.is-active]=\"cell.selected\"\r\n [attr.aria-selected]=\"cell.selected\"\r\n [flyTooltip]=\"cell.entry.label\"\r\n [attr.aria-label]=\"cell.entry.label\"\r\n (click)=\"pick(cell.entry)\">\r\n @if (section.animated) {\r\n <!-- Hover lives on the animated element, not the button: a template reference\r\n declared inside a control-flow block isn't visible to the block's parent. -->\r\n <fly-animated-emoji\r\n #anim\r\n class=\"fly-emoji-picker__glyph\"\r\n [id]=\"cell.entry.id\"\r\n [size]=\"20\"\r\n (pointerenter)=\"onCellHover(anim)\"\r\n (pointerleave)=\"cancelHover()\" />\r\n } @else {\r\n <span class=\"fly-emoji-picker__glyph\">{{ cell.glyph }}</span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n } @empty {\r\n <p class=\"fly-emoji-picker__empty\">{{ 'agent.lookup.no_results' | translate }}</p>\r\n }\r\n\r\n <!-- Lives INSIDE the scroller, as the last row, rather than pinned below it \u2014 a static\r\n footer row permanently ate into the vertical space actual emoji rows could use;\r\n scrolled into view instead, it only costs space once you reach the end. -->\r\n @if (hasMore()) {\r\n <button type=\"button\" class=\"fly-emoji-picker__more\" (click)=\"toggleExpanded()\">\r\n {{ (isExpanded() ? 'common.label.emoji_show_less' : 'common.label.emoji_show_more') | translate }}\r\n </button>\r\n }\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block;block-size:100%;-webkit-backdrop-filter:var(--glass2-blur);backdrop-filter:var(--glass2-blur)}@media(prefers-reduced-transparency:reduce){:host{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}:host:before,:host:after{display:none}}@media(prefers-contrast:more){:host{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){:host:after{animation:none}}:host{--fly-emoji-cell: 36px;--fly-emoji-cell-gap: 6px;--fly-emoji-glyph: 17px;--fly-emoji-section-gap: 14px;--fly-emoji-bar-gap: 8px;--fly-emoji-search-font: 12px;--fly-emoji-search-padding: 8px 10px;--fly-emoji-heading-display: block}:host(.fly-emoji-picker--medium){--fly-emoji-cell: 30px;--fly-emoji-cell-gap: 5px;--fly-emoji-glyph: 16px;--fly-emoji-section-gap: 10px;--fly-emoji-bar-gap: 6px;--fly-emoji-search-font: 11.5px;--fly-emoji-search-padding: 6px 9px}:host(.fly-emoji-picker--small){--fly-emoji-cell: 24px;--fly-emoji-cell-gap: 3px;--fly-emoji-glyph: 15px;--fly-emoji-section-gap: 6px;--fly-emoji-bar-gap: 5px;--fly-emoji-search-font: 11px;--fly-emoji-search-padding: 4px 7px;--fly-emoji-heading-display: none;--fly-emoji-picker-list-max: 132px}.fly-emoji-picker{display:flex;flex-direction:column;gap:var(--fly-emoji-bar-gap);box-sizing:border-box;block-size:100%;color:var(--text-color, inherit)}.fly-emoji-picker__bar{display:flex;align-items:center;gap:10px}.fly-emoji-picker__search{flex:1 1 auto;min-inline-size:0;box-sizing:border-box;padding:var(--fly-emoji-search-padding);font:inherit;font-size:var(--fly-emoji-search-font);border:1px solid var(--surface-border, rgba(255, 255, 255, .12));border-radius:5px;background:transparent;color:var(--text-color, inherit);outline:none}.fly-emoji-picker__search:focus{border-color:var(--primary-color)}.fly-emoji-picker__list{display:flex;flex:1 1 auto;flex-direction:column;gap:var(--fly-emoji-section-gap);min-block-size:0;max-block-size:var(--fly-emoji-picker-list-max, 320px);overflow-y:auto;padding-inline-end:4px}.fly-emoji-picker__section{display:flex;flex-direction:column;gap:6px}.fly-emoji-picker__heading{display:var(--fly-emoji-heading-display);position:sticky;inset-block-start:0;z-index:1;margin:0;padding-block:3px;font-size:10px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:var(--text-color-secondary, rgba(235, 235, 245, .55));background:var(--surface-overlay, var(--surface-card, #1c1c1e))}.fly-emoji-picker__tones{position:relative;flex:0 0 auto}.fly-emoji-picker__tone-trigger{display:flex;align-items:center;gap:2px;inline-size:auto;block-size:22px;padding:0 4px;font-size:14px;line-height:1;border:1px solid var(--surface-border, rgba(255, 255, 255, .12));border-radius:5px;background:transparent;color:var(--text-color-secondary, rgba(235, 235, 245, .55));cursor:pointer;transition:background .1s ease,border-color .1s ease}.fly-emoji-picker__tone-trigger:hover{background:var(--surface-hover, rgba(255, 255, 255, .08))}.fly-emoji-picker__tone-trigger:focus-visible{outline:2px solid var(--primary-color);outline-offset:-1px}.fly-emoji-picker__tone-glyph{line-height:1}.fly-emoji-picker__tone-caret{font-size:8px}.fly-emoji-picker__tone-menu{position:absolute;inset-block-start:calc(100% + 4px);inset-inline-end:0;z-index:21;display:grid;grid-template-columns:repeat(3,1fr);gap:2px;padding:4px;border-radius:8px;border:1px solid var(--glass2-border);background-image:radial-gradient(70% 60% at 50% 100%,var(--w07),transparent 70%),linear-gradient(180deg,var(--w08),transparent 20%),linear-gradient(180deg,var(--mat-menu-solid-a),var(--mat-menu-solid-b));box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset)}@media(prefers-reduced-transparency:reduce){.fly-emoji-picker__tone-menu{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.fly-emoji-picker__tone-menu:before,.fly-emoji-picker__tone-menu:after{display:none}}@media(prefers-contrast:more){.fly-emoji-picker__tone-menu{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.fly-emoji-picker__tone-menu:after{animation:none}}.fly-emoji-picker__tone-option{display:grid;place-items:center;inline-size:26px;block-size:26px;padding:0;font-size:15px;line-height:1;border:1px solid transparent;border-radius:5px;background:transparent;cursor:pointer;transition:background .1s ease,border-color .1s ease}.fly-emoji-picker__tone-option:hover{background:var(--surface-hover, rgba(255, 255, 255, .08))}.fly-emoji-picker__tone-option:focus-visible{outline:2px solid var(--primary-color);outline-offset:-1px}.fly-emoji-picker__tone-option.is-active{border-color:var(--primary-color);background:var(--surface-hover, rgba(255, 255, 255, .1))}.fly-emoji-picker__grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(var(--fly-emoji-cell),1fr));gap:var(--fly-emoji-cell-gap)}.fly-emoji-picker__cell{display:flex;align-items:center;justify-content:center;aspect-ratio:1;min-block-size:var(--fly-emoji-cell);padding:4px;border:1px solid transparent;border-radius:6px;background:transparent;color:var(--text-color, inherit);cursor:pointer;transition:background .1s ease,border-color .1s ease}.fly-emoji-picker__cell:hover{background:var(--surface-hover, rgba(255, 255, 255, .08));border-color:var(--surface-border, rgba(255, 255, 255, .12))}.fly-emoji-picker__cell:focus-visible{outline:2px solid var(--primary-color);outline-offset:-1px}.fly-emoji-picker__cell.is-active{border-color:var(--primary-color);background:var(--selection-bg, rgba(0, 113, 227, .24))}.fly-emoji-picker__glyph{direction:ltr;font-size:var(--fly-emoji-glyph);line-height:1}.fly-emoji-picker__more{align-self:flex-start;padding:3px 8px;font:inherit;font-size:11px;border:none;border-radius:999px;background:transparent;color:var(--primary-color);cursor:pointer}.fly-emoji-picker__more:hover{background:var(--surface-hover, rgba(255, 255, 255, .06))}.fly-emoji-picker__more:focus-visible{outline:2px solid var(--primary-color);outline-offset:1px}.fly-emoji-picker__empty{margin:0;padding:12px 4px;text-align:center;font-size:12px;color:var(--text-color-secondary, rgba(235, 235, 245, .5))}.fly-emoji-picker__tabs{display:flex;gap:4px;border-block-end:1px solid var(--surface-border, rgba(255, 255, 255, .12))}.fly-emoji-picker__tab{appearance:none;padding:6px 12px;font:inherit;font-size:12px;border:none;border-block-end:2px solid transparent;background:transparent;color:var(--text-color-secondary, rgba(235, 235, 245, .6));cursor:pointer}.fly-emoji-picker__tab.is-active{color:var(--text-color, inherit);border-block-end-color:var(--primary-color)}.fly-emoji-picker__tab:focus-visible{outline:2px solid var(--primary-color);outline-offset:-2px}.fly-emoji-picker__stickers{flex:1 1 auto;min-block-size:0;overflow-y:auto;display:grid;grid-template-columns:repeat(auto-fill,minmax(72px,1fr));gap:6px;padding:4px}.fly-emoji-picker__sticker{appearance:none;display:flex;align-items:center;justify-content:center;padding:4px;border:none;border-radius:10px;background:transparent;cursor:pointer}.fly-emoji-picker__sticker:hover,.fly-emoji-picker__sticker:focus-visible{background:var(--surface-hover, rgba(255, 255, 255, .08))}.fly-emoji-picker__sticker:focus-visible{outline:2px solid var(--primary-color);outline-offset:-2px}\n"], dependencies: [{ kind: "directive", type: FlyClickOutsideDirective, selector: "[flyClickOutside]", inputs: ["flyClickOutsideEnabled"], outputs: ["flyClickOutside"] }, { kind: "directive", type: FlyTooltipDirective, selector: "[flyTooltip], [data-tooltip]", inputs: ["flyTooltip", "flyTooltipPlacement", "flyTooltipDisabled", "flyTooltipDelay"] }, { kind: "component", type: FlyAnimatedEmojiComponent, selector: "fly-animated-emoji", inputs: ["id", "size", "autoplayOnFirstVisible", "playOnHover"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
7894
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyEmojiPickerComponent, isStandalone: true, selector: "fly-emoji-picker", inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, selectionMode: { classPropertyName: "selectionMode", publicName: "selectionMode", isSignal: true, isRequired: false, transformFunction: null }, selectedIds: { classPropertyName: "selectedIds", publicName: "selectedIds", isSignal: true, isRequired: false, transformFunction: null }, categories: { classPropertyName: "categories", publicName: "categories", isSignal: true, isRequired: false, transformFunction: null }, defaultCategory: { classPropertyName: "defaultCategory", publicName: "defaultCategory", isSignal: true, isRequired: false, transformFunction: null }, skinTones: { classPropertyName: "skinTones", publicName: "skinTones", isSignal: true, isRequired: false, transformFunction: null }, skinTone: { classPropertyName: "skinTone", publicName: "skinTone", isSignal: true, isRequired: false, transformFunction: null }, showMore: { classPropertyName: "showMore", publicName: "showMore", isSignal: true, isRequired: false, transformFunction: null }, previewCount: { classPropertyName: "previewCount", publicName: "previewCount", isSignal: true, isRequired: false, transformFunction: null }, emojiAnimation: { classPropertyName: "emojiAnimation", publicName: "emojiAnimation", isSignal: true, isRequired: false, transformFunction: null }, dismissOnOutsidePress: { classPropertyName: "dismissOnOutsidePress", publicName: "dismissOnOutsidePress", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { stickerPicked: "stickerPicked", toggled: "toggled", selectionChange: "selectionChange", skinToneChange: "skinToneChange", dismissed: "dismissed" }, host: { properties: { "class.fly-emoji-picker--small": "size() === 'small'", "class.fly-emoji-picker--medium": "size() === 'medium'", "class.fly-emoji-picker--large": "size() === 'large'" } }, viewQueries: [{ propertyName: "searchRef", first: true, predicate: ["searchRef"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\r\n class=\"fly-emoji-picker\"\r\n [flyClickOutsideEnabled]=\"dismissOnOutsidePress()\"\r\n (flyClickOutside)=\"dismissed.emit()\"\r\n [attr.aria-label]=\"'common.label.emoji_picker' | translate\">\r\n <!-- Stickers mode only: the emoji grid is unchanged, stickers are an ADDITIONAL surface. -->\r\n @if (showTabs()) {\r\n <div\r\n class=\"fly-emoji-picker__tabs\"\r\n role=\"tablist\"\r\n [attr.aria-label]=\"'common.label.emoji_tabs' | translate\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__tab\"\r\n role=\"tab\"\r\n id=\"fly-emoji-tab-emoji\"\r\n aria-controls=\"fly-emoji-panel-emoji\"\r\n [class.is-active]=\"activeTab() === 'emoji'\"\r\n [attr.aria-selected]=\"activeTab() === 'emoji'\"\r\n [attr.tabindex]=\"activeTab() === 'emoji' ? 0 : -1\"\r\n (keydown)=\"onTabKeydown($event)\"\r\n (click)=\"selectTab('emoji')\">\r\n {{ 'common.label.emoji_tab_emoji' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__tab\"\r\n role=\"tab\"\r\n id=\"fly-emoji-tab-stickers\"\r\n aria-controls=\"fly-emoji-panel-stickers\"\r\n [class.is-active]=\"activeTab() === 'stickers'\"\r\n [attr.aria-selected]=\"activeTab() === 'stickers'\"\r\n [attr.tabindex]=\"activeTab() === 'stickers' ? 0 : -1\"\r\n (keydown)=\"onTabKeydown($event)\"\r\n (click)=\"selectTab('stickers')\">\r\n {{ 'common.label.emoji_tab_stickers' | translate }}\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (showTabs() && activeTab() === 'stickers') {\r\n <div\r\n class=\"fly-emoji-picker__stickers\"\r\n id=\"fly-emoji-panel-stickers\"\r\n role=\"tabpanel\"\r\n aria-labelledby=\"fly-emoji-tab-stickers\">\r\n @for (entry of stickers(); track entry.id) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__sticker\"\r\n [flyTooltip]=\"entry.label\"\r\n [attr.aria-label]=\"entry.label\"\r\n (pointerenter)=\"onCellHover(sticker)\"\r\n (pointerleave)=\"cancelHover()\"\r\n (click)=\"pickSticker(entry)\">\r\n <fly-animated-emoji #sticker [id]=\"entry.id\" [size]=\"64\" />\r\n </button>\r\n } @empty {\r\n <p class=\"fly-emoji-picker__empty\">{{ 'agent.lookup.no_results' | translate }}</p>\r\n }\r\n </div>\r\n }\r\n\r\n @if (!showTabs() || activeTab() === 'emoji') {\r\n <!-- Search + tone sit together as one chrome bar: the tone swatches are hands, and on their own row\r\n above the grid they read as pickable content rather than a setting. -->\r\n <div class=\"fly-emoji-picker__bar\">\r\n <input\r\n class=\"fly-emoji-picker__search\"\r\n type=\"text\"\r\n #searchRef\r\n [value]=\"query()\"\r\n (input)=\"onQueryInput(searchRef.value)\"\r\n [placeholder]=\"'common.label.search_emoji' | translate\"\r\n [attr.aria-label]=\"'common.label.search_emoji' | translate\"\r\n spellcheck=\"false\"\r\n autocomplete=\"off\" />\r\n\r\n @if (showToneStrip()) {\r\n <!-- Escape-to-close listener only \u2014 every actual control inside (trigger + options) is its\r\n own focusable button, so this wrapper itself doesn't need to be. -->\r\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\r\n <div\r\n class=\"fly-emoji-picker__tones\"\r\n [flyClickOutsideEnabled]=\"toneMenuOpen()\"\r\n (flyClickOutside)=\"toneMenuOpen.set(false)\"\r\n (keydown.escape)=\"toneMenuOpen.set(false)\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__tone-trigger\"\r\n aria-haspopup=\"listbox\"\r\n [attr.aria-expanded]=\"toneMenuOpen()\"\r\n [attr.aria-label]=\"'common.label.skin_tone' | translate\"\r\n [flyTooltip]=\"toneLabelKey(effectiveTone()) | translate\"\r\n (click)=\"toneMenuOpen.set(!toneMenuOpen())\">\r\n <span class=\"fly-emoji-picker__tone-glyph\">{{ toneSwatch(effectiveTone()) }}</span>\r\n <i class=\"pi pi-chevron-down fly-emoji-picker__tone-caret\" aria-hidden=\"true\"></i>\r\n </button>\r\n\r\n @if (toneMenuOpen()) {\r\n <div\r\n class=\"fly-emoji-picker__tone-menu\"\r\n role=\"listbox\"\r\n [attr.aria-label]=\"'common.label.skin_tone' | translate\">\r\n @for (tone of tones; track tone) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__tone-option\"\r\n role=\"option\"\r\n [class.is-active]=\"effectiveTone() === tone\"\r\n [attr.aria-selected]=\"effectiveTone() === tone\"\r\n [flyTooltip]=\"toneLabelKey(tone) | translate\"\r\n (click)=\"onToneOptionClick(tone)\">\r\n {{ toneSwatch(tone) }}\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- ONE scrolling list; each category is a separator label over its own grid. Tabs wrapped to three\r\n rows in a sidebar drawer and cost a click to reach any group. -->\r\n <div\r\n class=\"fly-emoji-picker__list\"\r\n role=\"listbox\"\r\n [attr.id]=\"showTabs() ? 'fly-emoji-panel-emoji' : null\"\r\n [attr.aria-labelledby]=\"showTabs() ? 'fly-emoji-tab-emoji' : null\"\r\n [attr.aria-multiselectable]=\"selectionMode() === 'multi'\">\r\n @for (section of sections(); track section.id) {\r\n <div class=\"fly-emoji-picker__section\" role=\"group\" [attr.aria-label]=\"sectionLabel(section)\">\r\n <h4 class=\"fly-emoji-picker__heading\">{{ sectionLabel(section) }}</h4>\r\n <div class=\"fly-emoji-picker__grid\">\r\n @for (cell of section.cells; track cell.entry.id) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-emoji-picker__cell\"\r\n role=\"option\"\r\n [class.is-active]=\"cell.selected\"\r\n [attr.aria-selected]=\"cell.selected\"\r\n [flyTooltip]=\"cell.entry.label\"\r\n [attr.aria-label]=\"cell.entry.label\"\r\n (click)=\"pick(cell.entry)\">\r\n @if (section.animated) {\r\n <!-- Hover lives on the animated element, not the button: a template reference\r\n declared inside a control-flow block isn't visible to the block's parent. -->\r\n <fly-animated-emoji\r\n #anim\r\n class=\"fly-emoji-picker__glyph\"\r\n [id]=\"cell.entry.id\"\r\n [size]=\"20\"\r\n (pointerenter)=\"onCellHover(anim)\"\r\n (pointerleave)=\"cancelHover()\" />\r\n } @else {\r\n <span class=\"fly-emoji-picker__glyph\">{{ cell.glyph }}</span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n } @empty {\r\n <p class=\"fly-emoji-picker__empty\">{{ 'agent.lookup.no_results' | translate }}</p>\r\n }\r\n\r\n <!-- Lives INSIDE the scroller, as the last row, rather than pinned below it \u2014 a static\r\n footer row permanently ate into the vertical space actual emoji rows could use;\r\n scrolled into view instead, it only costs space once you reach the end. -->\r\n @if (hasMore()) {\r\n <button type=\"button\" class=\"fly-emoji-picker__more\" (click)=\"toggleExpanded()\">\r\n {{ (isExpanded() ? 'common.label.emoji_show_less' : 'common.label.emoji_show_more') | translate }}\r\n </button>\r\n }\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block;block-size:100%;-webkit-backdrop-filter:var(--glass2-blur);backdrop-filter:var(--glass2-blur)}@media(prefers-reduced-transparency:reduce){:host{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}:host:before,:host:after{display:none}}@media(prefers-contrast:more){:host{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){:host:after{animation:none}}:host{--fly-emoji-cell: 36px;--fly-emoji-cell-gap: 6px;--fly-emoji-glyph: 17px;--fly-emoji-section-gap: 14px;--fly-emoji-bar-gap: 8px;--fly-emoji-search-font: 12px;--fly-emoji-search-padding: 8px 10px;--fly-emoji-heading-display: block}:host(.fly-emoji-picker--medium){--fly-emoji-cell: 30px;--fly-emoji-cell-gap: 5px;--fly-emoji-glyph: 16px;--fly-emoji-section-gap: 10px;--fly-emoji-bar-gap: 6px;--fly-emoji-search-font: 11.5px;--fly-emoji-search-padding: 6px 9px}:host(.fly-emoji-picker--small){--fly-emoji-cell: 24px;--fly-emoji-cell-gap: 3px;--fly-emoji-glyph: 15px;--fly-emoji-section-gap: 6px;--fly-emoji-bar-gap: 5px;--fly-emoji-search-font: 11px;--fly-emoji-search-padding: 4px 7px;--fly-emoji-heading-display: none;--fly-emoji-picker-list-max: 132px}.fly-emoji-picker{display:flex;flex-direction:column;gap:var(--fly-emoji-bar-gap);box-sizing:border-box;block-size:100%;color:var(--text-color, inherit)}.fly-emoji-picker__bar{display:flex;align-items:center;gap:10px}.fly-emoji-picker__search{flex:1 1 auto;min-inline-size:0;box-sizing:border-box;padding:var(--fly-emoji-search-padding);font:inherit;font-size:var(--fly-emoji-search-font);border:1px solid var(--surface-border, rgba(255, 255, 255, .12));border-radius:5px;background:transparent;color:var(--text-color, inherit);outline:none}.fly-emoji-picker__search:focus{border-color:var(--primary-color)}.fly-emoji-picker__list{display:flex;flex:1 1 auto;flex-direction:column;gap:var(--fly-emoji-section-gap);min-block-size:0;max-block-size:var(--fly-emoji-picker-list-max, 320px);overflow-y:auto;padding-inline-end:4px}.fly-emoji-picker__section{display:flex;flex-direction:column;gap:6px}.fly-emoji-picker__heading{display:var(--fly-emoji-heading-display);position:sticky;inset-block-start:0;z-index:1;margin:0;padding-block:3px;font-size:10px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:var(--text-color-secondary, rgba(235, 235, 245, .55));background:var(--surface-overlay, var(--surface-card, #1c1c1e))}.fly-emoji-picker__tones{position:relative;flex:0 0 auto}.fly-emoji-picker__tone-trigger{display:flex;align-items:center;gap:2px;inline-size:auto;block-size:22px;padding:0 4px;font-size:14px;line-height:1;border:1px solid var(--surface-border, rgba(255, 255, 255, .12));border-radius:5px;background:transparent;color:var(--text-color-secondary, rgba(235, 235, 245, .55));cursor:pointer;transition:background .1s ease,border-color .1s ease}.fly-emoji-picker__tone-trigger:hover{background:var(--surface-hover, rgba(255, 255, 255, .08))}.fly-emoji-picker__tone-trigger:focus-visible{outline:2px solid var(--primary-color);outline-offset:-1px}.fly-emoji-picker__tone-glyph{line-height:1}.fly-emoji-picker__tone-caret{font-size:8px}.fly-emoji-picker__tone-menu{position:absolute;inset-block-start:calc(100% + 4px);inset-inline-end:0;z-index:21;display:grid;grid-template-columns:repeat(3,1fr);gap:2px;padding:4px;border-radius:8px;border:1px solid var(--glass2-border);background-image:radial-gradient(70% 60% at 50% 100%,var(--w07),transparent 70%),linear-gradient(180deg,var(--w08),transparent 20%),linear-gradient(180deg,var(--mat-menu-solid-a),var(--mat-menu-solid-b));box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset)}@media(prefers-reduced-transparency:reduce){.fly-emoji-picker__tone-menu{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.fly-emoji-picker__tone-menu:before,.fly-emoji-picker__tone-menu:after{display:none}}@media(prefers-contrast:more){.fly-emoji-picker__tone-menu{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.fly-emoji-picker__tone-menu:after{animation:none}}.fly-emoji-picker__tone-option{display:grid;place-items:center;inline-size:26px;block-size:26px;padding:0;font-size:15px;line-height:1;border:1px solid transparent;border-radius:5px;background:transparent;cursor:pointer;transition:background .1s ease,border-color .1s ease}.fly-emoji-picker__tone-option:hover{background:var(--surface-hover, rgba(255, 255, 255, .08))}.fly-emoji-picker__tone-option:focus-visible{outline:2px solid var(--primary-color);outline-offset:-1px}.fly-emoji-picker__tone-option.is-active{border-color:var(--primary-color);background:var(--surface-hover, rgba(255, 255, 255, .1))}.fly-emoji-picker__grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(var(--fly-emoji-cell),1fr));gap:var(--fly-emoji-cell-gap)}.fly-emoji-picker__cell{display:flex;align-items:center;justify-content:center;aspect-ratio:1;min-block-size:var(--fly-emoji-cell);padding:4px;border:1px solid transparent;border-radius:6px;background:transparent;color:var(--text-color, inherit);cursor:pointer;transition:background .1s ease,border-color .1s ease}.fly-emoji-picker__cell:hover{background:var(--surface-hover, rgba(255, 255, 255, .08));border-color:var(--surface-border, rgba(255, 255, 255, .12))}.fly-emoji-picker__cell:focus-visible{outline:2px solid var(--primary-color);outline-offset:-1px}.fly-emoji-picker__cell.is-active{border-color:var(--primary-color);background:var(--selection-bg, rgba(0, 113, 227, .24))}.fly-emoji-picker__glyph{direction:ltr;font-size:var(--fly-emoji-glyph);line-height:1}.fly-emoji-picker__more{align-self:flex-start;padding:3px 8px;font:inherit;font-size:11px;border:none;border-radius:999px;background:transparent;color:var(--primary-color);cursor:pointer}.fly-emoji-picker__more:hover{background:var(--surface-hover, rgba(255, 255, 255, .06))}.fly-emoji-picker__more:focus-visible{outline:2px solid var(--primary-color);outline-offset:1px}.fly-emoji-picker__empty{margin:0;padding:12px 4px;text-align:center;font-size:12px;color:var(--text-color-secondary, rgba(235, 235, 245, .5))}.fly-emoji-picker__tabs{display:flex;gap:4px;border-block-end:1px solid var(--surface-border, rgba(255, 255, 255, .12))}.fly-emoji-picker__tab{appearance:none;padding:6px 12px;font:inherit;font-size:12px;border:none;border-block-end:2px solid transparent;background:transparent;color:var(--text-color-secondary, rgba(235, 235, 245, .6));cursor:pointer}.fly-emoji-picker__tab.is-active{color:var(--text-color, inherit);border-block-end-color:var(--primary-color)}.fly-emoji-picker__tab:focus-visible{outline:2px solid var(--primary-color);outline-offset:-2px}.fly-emoji-picker__stickers{flex:1 1 auto;min-block-size:0;overflow-y:auto;display:grid;grid-template-columns:repeat(auto-fill,minmax(72px,1fr));gap:6px;padding:4px}.fly-emoji-picker__sticker{appearance:none;display:flex;align-items:center;justify-content:center;padding:4px;border:none;border-radius:10px;background:transparent;cursor:pointer}.fly-emoji-picker__sticker:hover,.fly-emoji-picker__sticker:focus-visible{background:var(--surface-hover, rgba(255, 255, 255, .08))}.fly-emoji-picker__sticker:focus-visible{outline:2px solid var(--primary-color);outline-offset:-2px}\n"], dependencies: [{ kind: "directive", type: FlyClickOutsideDirective, selector: "[flyClickOutside]", inputs: ["flyClickOutsideEnabled", "flyClickOutsideIgnore"], outputs: ["flyClickOutside"] }, { kind: "directive", type: FlyTooltipDirective, selector: "[flyTooltip], [data-tooltip]", inputs: ["flyTooltip", "flyTooltipPlacement", "flyTooltipDisabled", "flyTooltipDelay"] }, { kind: "component", type: FlyAnimatedEmojiComponent, selector: "fly-animated-emoji", inputs: ["id", "size", "autoplayOnFirstVisible", "playOnHover"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
7861
7895
|
}
|
|
7862
7896
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyEmojiPickerComponent, decorators: [{
|
|
7863
7897
|
type: Component,
|
|
@@ -9949,15 +9983,144 @@ class FlyBreadcrumbComponent {
|
|
|
9949
9983
|
this.navigate.emit(item);
|
|
9950
9984
|
}
|
|
9951
9985
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyBreadcrumbComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9952
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyBreadcrumbComponent, isStandalone: true, selector: "fly-breadcrumb", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, maxVisible: { classPropertyName: "maxVisible", publicName: "maxVisible", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, hasCurrentPage: { classPropertyName: "hasCurrentPage", publicName: "hasCurrentPage", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { navigate: "navigate" }, host: { properties: { "attr.dir": "i18n.direction()" } }, ngImport: i0, template: "<!--\r\n The CURRENT-PAGE crumb is ALWAYS plain text with aria-current=\"page\" \u2014 never an <a>\r\n or <button>, even when it carries an href. With [hasCurrentPage]=\"false\" the trail\r\n lists ancestors only, so no crumb is the current page and every one stays a link.\r\n The separator is decorative (aria-hidden) and drawn as a CSS mask so it mirrors\r\n under RTL with no directional glyph baked in.\r\n-->\r\n<nav class=\"fly-breadcrumb\" [attr.aria-label]=\"resolvedAriaLabel()\">\r\n
|
|
9986
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyBreadcrumbComponent, isStandalone: true, selector: "fly-breadcrumb", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, maxVisible: { classPropertyName: "maxVisible", publicName: "maxVisible", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, hasCurrentPage: { classPropertyName: "hasCurrentPage", publicName: "hasCurrentPage", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { navigate: "navigate" }, host: { properties: { "attr.dir": "i18n.direction()" } }, ngImport: i0, template: "<!--\r\n The CURRENT-PAGE crumb is ALWAYS plain text with aria-current=\"page\" \u2014 never an <a>\r\n or <button>, even when it carries an href. With [hasCurrentPage]=\"false\" the trail\r\n lists ancestors only, so no crumb is the current page and every one stays a link.\r\n The separator is decorative (aria-hidden) and drawn as a CSS mask so it mirrors\r\n under RTL with no directional glyph baked in.\r\n-->\r\n<nav class=\"fly-breadcrumb\" [attr.aria-label]=\"resolvedAriaLabel()\">\r\n @for (entry of renderItems(); track entry.kind === 'item' ? entry.item.id : 'overflow'; let i = $index) {\r\n @if (i > 0) {\r\n <svg class=\"fly-breadcrumb__separator\" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\">\r\n <path d=\"M9 5l7 7-7 7\" />\r\n </svg>\r\n }\r\n @if (entry.kind === 'overflow') {\r\n <span class=\"fly-breadcrumb__crumb fly-breadcrumb__crumb--overflow\">\u2026</span>\r\n } @else if (entry.isCurrent) {\r\n <span class=\"fly-breadcrumb__crumb fly-breadcrumb__crumb--current\" aria-current=\"page\">\r\n @if (entry.item.iconPath; as d) {\r\n <svg class=\"fly-breadcrumb__icon\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\"><path [attr.d]=\"d\" /></svg>\r\n }\r\n {{ entry.item.label }}\r\n </span>\r\n } @else {\r\n <span class=\"fly-breadcrumb__crumb\">\r\n @if (entry.item.href) {\r\n <a class=\"fly-breadcrumb__link\" [attr.href]=\"entry.item.href\"\r\n (click)=\"onActivate(entry.item)\">\r\n @if (entry.item.iconPath; as d) {\r\n <svg class=\"fly-breadcrumb__icon\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\"><path [attr.d]=\"d\" /></svg>\r\n }\r\n {{ entry.item.label }}\r\n </a>\r\n } @else {\r\n <button type=\"button\" class=\"fly-breadcrumb__link fly-breadcrumb__link--button\"\r\n (click)=\"onActivate(entry.item)\">\r\n @if (entry.item.iconPath; as d) {\r\n <svg class=\"fly-breadcrumb__icon\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\"><path [attr.d]=\"d\" /></svg>\r\n }\r\n {{ entry.item.label }}\r\n </button>\r\n }\r\n </span>\r\n }\r\n }\r\n</nav>\r\n", styles: [":host{display:block;min-inline-size:0}.fly-breadcrumb{display:flex;align-items:center;gap:2px;min-inline-size:0}.fly-breadcrumb__crumb{display:flex;align-items:center;flex:none;min-inline-size:0;font-size:12.5px;line-height:1.4}.fly-breadcrumb__crumb--current{flex:1 1 auto;min-inline-size:0;gap:6px;block-size:26px;padding-inline:4px;font-weight:600;letter-spacing:-.006em;color:var(--text-color);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host([dir=rtl]) .fly-breadcrumb__crumb--current{letter-spacing:normal}.fly-breadcrumb__crumb--overflow{color:var(--text-color-secondary);-webkit-user-select:none;user-select:none}.fly-breadcrumb__link{display:inline-flex;align-items:center;gap:6px;block-size:26px;padding-inline:8px;margin:0;border:0;border-radius:8px;background:transparent;color:var(--text-color-secondary);font-family:inherit;font-size:12.5px;font-weight:500;line-height:1;text-decoration:none;cursor:pointer;max-inline-size:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:background var(--nova-duration-hover) var(--nova-ease-micro),color var(--nova-duration-hover) var(--nova-ease-micro)}.fly-breadcrumb__link:hover{background:var(--w08);color:var(--text-color)}.fly-breadcrumb__link:focus-visible{outline:2px solid var(--primary-color);outline-offset:1px}.fly-breadcrumb__link--button{display:inline-flex}.fly-breadcrumb__icon{flex:none}.fly-breadcrumb__separator{flex:none;color:var(--w28)}:host([dir=rtl]) .fly-breadcrumb__separator{transform:scaleX(-1)}@media(forced-colors:active){.fly-breadcrumb__separator{color:CanvasText}.fly-breadcrumb__link:focus-visible{outline-color:Highlight}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
9953
9987
|
}
|
|
9954
9988
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyBreadcrumbComponent, decorators: [{
|
|
9955
9989
|
type: Component,
|
|
9956
9990
|
args: [{ selector: 'fly-breadcrumb', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
9957
9991
|
'[attr.dir]': 'i18n.direction()',
|
|
9958
|
-
}, template: "<!--\r\n The CURRENT-PAGE crumb is ALWAYS plain text with aria-current=\"page\" \u2014 never an <a>\r\n or <button>, even when it carries an href. With [hasCurrentPage]=\"false\" the trail\r\n lists ancestors only, so no crumb is the current page and every one stays a link.\r\n The separator is decorative (aria-hidden) and drawn as a CSS mask so it mirrors\r\n under RTL with no directional glyph baked in.\r\n-->\r\n<nav class=\"fly-breadcrumb\" [attr.aria-label]=\"resolvedAriaLabel()\">\r\n
|
|
9992
|
+
}, template: "<!--\r\n The CURRENT-PAGE crumb is ALWAYS plain text with aria-current=\"page\" \u2014 never an <a>\r\n or <button>, even when it carries an href. With [hasCurrentPage]=\"false\" the trail\r\n lists ancestors only, so no crumb is the current page and every one stays a link.\r\n The separator is decorative (aria-hidden) and drawn as a CSS mask so it mirrors\r\n under RTL with no directional glyph baked in.\r\n-->\r\n<nav class=\"fly-breadcrumb\" [attr.aria-label]=\"resolvedAriaLabel()\">\r\n @for (entry of renderItems(); track entry.kind === 'item' ? entry.item.id : 'overflow'; let i = $index) {\r\n @if (i > 0) {\r\n <svg class=\"fly-breadcrumb__separator\" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\">\r\n <path d=\"M9 5l7 7-7 7\" />\r\n </svg>\r\n }\r\n @if (entry.kind === 'overflow') {\r\n <span class=\"fly-breadcrumb__crumb fly-breadcrumb__crumb--overflow\">\u2026</span>\r\n } @else if (entry.isCurrent) {\r\n <span class=\"fly-breadcrumb__crumb fly-breadcrumb__crumb--current\" aria-current=\"page\">\r\n @if (entry.item.iconPath; as d) {\r\n <svg class=\"fly-breadcrumb__icon\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\"><path [attr.d]=\"d\" /></svg>\r\n }\r\n {{ entry.item.label }}\r\n </span>\r\n } @else {\r\n <span class=\"fly-breadcrumb__crumb\">\r\n @if (entry.item.href) {\r\n <a class=\"fly-breadcrumb__link\" [attr.href]=\"entry.item.href\"\r\n (click)=\"onActivate(entry.item)\">\r\n @if (entry.item.iconPath; as d) {\r\n <svg class=\"fly-breadcrumb__icon\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\"><path [attr.d]=\"d\" /></svg>\r\n }\r\n {{ entry.item.label }}\r\n </a>\r\n } @else {\r\n <button type=\"button\" class=\"fly-breadcrumb__link fly-breadcrumb__link--button\"\r\n (click)=\"onActivate(entry.item)\">\r\n @if (entry.item.iconPath; as d) {\r\n <svg class=\"fly-breadcrumb__icon\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\"><path [attr.d]=\"d\" /></svg>\r\n }\r\n {{ entry.item.label }}\r\n </button>\r\n }\r\n </span>\r\n }\r\n }\r\n</nav>\r\n", styles: [":host{display:block;min-inline-size:0}.fly-breadcrumb{display:flex;align-items:center;gap:2px;min-inline-size:0}.fly-breadcrumb__crumb{display:flex;align-items:center;flex:none;min-inline-size:0;font-size:12.5px;line-height:1.4}.fly-breadcrumb__crumb--current{flex:1 1 auto;min-inline-size:0;gap:6px;block-size:26px;padding-inline:4px;font-weight:600;letter-spacing:-.006em;color:var(--text-color);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host([dir=rtl]) .fly-breadcrumb__crumb--current{letter-spacing:normal}.fly-breadcrumb__crumb--overflow{color:var(--text-color-secondary);-webkit-user-select:none;user-select:none}.fly-breadcrumb__link{display:inline-flex;align-items:center;gap:6px;block-size:26px;padding-inline:8px;margin:0;border:0;border-radius:8px;background:transparent;color:var(--text-color-secondary);font-family:inherit;font-size:12.5px;font-weight:500;line-height:1;text-decoration:none;cursor:pointer;max-inline-size:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:background var(--nova-duration-hover) var(--nova-ease-micro),color var(--nova-duration-hover) var(--nova-ease-micro)}.fly-breadcrumb__link:hover{background:var(--w08);color:var(--text-color)}.fly-breadcrumb__link:focus-visible{outline:2px solid var(--primary-color);outline-offset:1px}.fly-breadcrumb__link--button{display:inline-flex}.fly-breadcrumb__icon{flex:none}.fly-breadcrumb__separator{flex:none;color:var(--w28)}:host([dir=rtl]) .fly-breadcrumb__separator{transform:scaleX(-1)}@media(forced-colors:active){.fly-breadcrumb__separator{color:CanvasText}.fly-breadcrumb__link:focus-visible{outline-color:Highlight}}\n"] }]
|
|
9959
9993
|
}], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], maxVisible: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxVisible", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], hasCurrentPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasCurrentPage", required: false }] }], navigate: [{ type: i0.Output, args: ["navigate"] }] } });
|
|
9960
9994
|
|
|
9995
|
+
/** Cross-bundle store key + sync event — same shape as `FLY_MAGIC_BAR_STORE_KEY`. */
|
|
9996
|
+
const FLY_WINDOW_BREADCRUMBS_STORE_KEY = '__flyWindowBreadcrumbs__';
|
|
9997
|
+
const FLY_WINDOW_BREADCRUMBS_EVENT = 'fly:window-breadcrumbs';
|
|
9998
|
+
function store$1() {
|
|
9999
|
+
const g = globalThis;
|
|
10000
|
+
return (g[FLY_WINDOW_BREADCRUMBS_STORE_KEY] ??= { trails: {}, revision: 0 });
|
|
10001
|
+
}
|
|
10002
|
+
function emitSync$1() {
|
|
10003
|
+
const g = globalThis;
|
|
10004
|
+
try {
|
|
10005
|
+
g.dispatchEvent?.(new Event(FLY_WINDOW_BREADCRUMBS_EVENT));
|
|
10006
|
+
}
|
|
10007
|
+
catch {
|
|
10008
|
+
// Non-DOM realm (SSR / a unit env without `Event`) — see the identical note on
|
|
10009
|
+
// `MagicBarRegistry.emitSync`. The instance that mutated the store is
|
|
10010
|
+
// unaffected; any OTHER instance in such a realm stays stale until its own
|
|
10011
|
+
// next mutation calls `sync()` directly.
|
|
10012
|
+
}
|
|
10013
|
+
}
|
|
10014
|
+
/**
|
|
10015
|
+
* Where an app publishes the breadcrumb trail its window's header strip renders.
|
|
10016
|
+
*
|
|
10017
|
+
* The design puts a trail inside the window
|
|
10018
|
+
* (`UX/FlyOS Desktop-app/MainContent.dc.html`, `nav[aria-label="Breadcrumb"]`), and
|
|
10019
|
+
* the window chrome (`WindowComponent`) renders whatever this registry holds for
|
|
10020
|
+
* its window id, falling back to the plain window title when nothing has published.
|
|
10021
|
+
*
|
|
10022
|
+
* ## Why the state lives on `globalThis`
|
|
10023
|
+
*
|
|
10024
|
+
* Same reasoning as {@link MagicBarRegistry} (`services/magic-bar/`), and the same
|
|
10025
|
+
* direction of travel (remote → shell chrome). This service is `providedIn: 'root'`,
|
|
10026
|
+
* but a root instance is not *reliably* shared across the federation boundary: the
|
|
10027
|
+
* shell builds the design system from workspace source while remotes consume the
|
|
10028
|
+
* published package, and Native Federation collapses those into one runtime
|
|
10029
|
+
* instance only when version negotiation succeeds — a split there is invisible at
|
|
10030
|
+
* build time and would otherwise present as a window whose breadcrumb silently
|
|
10031
|
+
* never appears, for federated apps only. Keeping the state on the one substrate
|
|
10032
|
+
* every bundle shares makes this store fork-proof: whichever copy of this service
|
|
10033
|
+
* publishes, every copy's `trail()` agrees. Each instance mirrors the store into a
|
|
10034
|
+
* local signal and re-reads on the sync event, so `WindowComponent`'s template —
|
|
10035
|
+
* which reads `trail()` inside its own reactive context — repaints whichever
|
|
10036
|
+
* bundle actually published.
|
|
10037
|
+
*
|
|
10038
|
+
* Simpler than `MagicBarRegistry` on purpose: a breadcrumb trail is addressed
|
|
10039
|
+
* directly by the window id its publisher already has (via `WINDOW_DATA`), so
|
|
10040
|
+
* there is no owner-vs-active-window arbitration, no per-action indexing and no
|
|
10041
|
+
* publish-fingerprint dedup to carry — `publish()` just replaces the entry.
|
|
10042
|
+
*
|
|
10043
|
+
* ```ts
|
|
10044
|
+
* const win = inject(WINDOW_DATA);
|
|
10045
|
+
* const crumbs = inject(WindowBreadcrumbsRegistry);
|
|
10046
|
+
* crumbs.publish(win.id, [
|
|
10047
|
+
* { id: 'root', label: circleName, navigate: () => this.showList() },
|
|
10048
|
+
* { id: 'trend', label: trend.title },
|
|
10049
|
+
* ]);
|
|
10050
|
+
* ```
|
|
10051
|
+
*/
|
|
10052
|
+
class WindowBreadcrumbsRegistry {
|
|
10053
|
+
_trails = signal(new Map(), ...(ngDevMode ? [{ debugName: "_trails" }] : /* istanbul ignore next */ []));
|
|
10054
|
+
_syncedStore = null;
|
|
10055
|
+
_syncedRevision = -1;
|
|
10056
|
+
/** Every published trail, keyed by window id. Reactive — read it inside a template/computed/effect. */
|
|
10057
|
+
all = this._trails.asReadonly();
|
|
10058
|
+
constructor() {
|
|
10059
|
+
const g = globalThis;
|
|
10060
|
+
g.addEventListener?.(FLY_WINDOW_BREADCRUMBS_EVENT, () => this.sync());
|
|
10061
|
+
this.sync();
|
|
10062
|
+
}
|
|
10063
|
+
/**
|
|
10064
|
+
* This window's published trail, or an empty array.
|
|
10065
|
+
*
|
|
10066
|
+
* A plain method over a signal read, not a per-id `computed`: it is called from
|
|
10067
|
+
* the window template, where the signal read is tracked whatever the call depth,
|
|
10068
|
+
* and a `computed` cache keyed by a mutable window id would outlive its window.
|
|
10069
|
+
*/
|
|
10070
|
+
trail(windowId) {
|
|
10071
|
+
return this._trails().get(windowId) ?? EMPTY_TRAIL;
|
|
10072
|
+
}
|
|
10073
|
+
/**
|
|
10074
|
+
* Replace a window's trail. Publishing an empty array is how an app goes back to
|
|
10075
|
+
* showing the plain window title — it is a state, not a clear.
|
|
10076
|
+
*/
|
|
10077
|
+
publish(windowId, crumbs) {
|
|
10078
|
+
const s = store$1();
|
|
10079
|
+
s.trails = { ...s.trails, [windowId]: [...crumbs] };
|
|
10080
|
+
s.revision++;
|
|
10081
|
+
this.sync();
|
|
10082
|
+
emitSync$1();
|
|
10083
|
+
}
|
|
10084
|
+
/** Drop a window's trail entirely. The window chrome calls this when it is destroyed. */
|
|
10085
|
+
clear(windowId) {
|
|
10086
|
+
const s = store$1();
|
|
10087
|
+
if (!(windowId in s.trails))
|
|
10088
|
+
return;
|
|
10089
|
+
const trails = { ...s.trails };
|
|
10090
|
+
delete trails[windowId];
|
|
10091
|
+
s.trails = trails;
|
|
10092
|
+
s.revision++;
|
|
10093
|
+
this.sync();
|
|
10094
|
+
emitSync$1();
|
|
10095
|
+
}
|
|
10096
|
+
/**
|
|
10097
|
+
* Re-read the shared store. Cheap no-op unless a real mutation landed.
|
|
10098
|
+
*
|
|
10099
|
+
* Keys on store IDENTITY as well as revision — see the identical note on
|
|
10100
|
+
* `MagicBarRegistry.sync`: a revision counter alone cannot distinguish "nothing
|
|
10101
|
+
* changed" from "the `globalThis` slot now holds a brand-new store that happens
|
|
10102
|
+
* to be at the same revision" (every spec's `beforeEach` replaces the store).
|
|
10103
|
+
*/
|
|
10104
|
+
sync() {
|
|
10105
|
+
const s = store$1();
|
|
10106
|
+
if (s === this._syncedStore && s.revision === this._syncedRevision)
|
|
10107
|
+
return;
|
|
10108
|
+
this._syncedStore = s;
|
|
10109
|
+
this._syncedRevision = s.revision;
|
|
10110
|
+
this._trails.set(new Map(Object.entries(s.trails)));
|
|
10111
|
+
}
|
|
10112
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: WindowBreadcrumbsRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
10113
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: WindowBreadcrumbsRegistry, providedIn: 'root' });
|
|
10114
|
+
}
|
|
10115
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: WindowBreadcrumbsRegistry, decorators: [{
|
|
10116
|
+
type: Injectable,
|
|
10117
|
+
args: [{ providedIn: 'root' }]
|
|
10118
|
+
}], ctorParameters: () => [] });
|
|
10119
|
+
/** Shared empty result — a fresh `[]` per read would re-render every window on every pass. */
|
|
10120
|
+
const EMPTY_TRAIL = Object.freeze([]);
|
|
10121
|
+
|
|
10122
|
+
/** Public window-breadcrumbs surface — see `window-breadcrumbs-registry.service.ts`. */
|
|
10123
|
+
|
|
9961
10124
|
/**
|
|
9962
10125
|
* Headless keyboard-navigation logic — roving focus for menus, radiogroups, and
|
|
9963
10126
|
* tablists.
|
|
@@ -12746,6 +12909,53 @@ function resolveLinks(deps, renderableIds) {
|
|
|
12746
12909
|
}
|
|
12747
12910
|
return out;
|
|
12748
12911
|
}
|
|
12912
|
+
/** Straight run (px) a dependency elbow travels out of a bar edge before it turns. */
|
|
12913
|
+
const LINK_STUB = 12;
|
|
12914
|
+
/**
|
|
12915
|
+
* Relationship kind implied by the edge a gesture left and the edge it landed on. The name is
|
|
12916
|
+
* literally `<predecessor endpoint><successor endpoint>`, so this is a lookup, not a rule.
|
|
12917
|
+
*/
|
|
12918
|
+
function linkTypeFor(from, to) {
|
|
12919
|
+
if (from === 'finish')
|
|
12920
|
+
return to === 'start' ? 'FS' : 'FF';
|
|
12921
|
+
return to === 'start' ? 'SS' : 'SF';
|
|
12922
|
+
}
|
|
12923
|
+
/**
|
|
12924
|
+
* Route a dependency elbow for any of the four relationship kinds (render space).
|
|
12925
|
+
*
|
|
12926
|
+
* The kind names the two endpoints, so it decides both the anchor x's and the direction the
|
|
12927
|
+
* arrow travels: leaving a *finish* edge moves forward in time, leaving a *start* edge moves
|
|
12928
|
+
* backward; entering a *start* edge approaches from behind, entering a *finish* edge from
|
|
12929
|
+
* ahead. `fwd` folds RTL in, so none of that branches on direction.
|
|
12930
|
+
*
|
|
12931
|
+
* One turn suffices when the entry stub lies ahead of the exit stub in the approach
|
|
12932
|
+
* direction. When it does not — overlapping or reversed bars, which SS/FF/SF hit constantly —
|
|
12933
|
+
* a straight drop would cut back through the bars, so the elbow detours via the gap between
|
|
12934
|
+
* the two rows instead.
|
|
12935
|
+
*/
|
|
12936
|
+
function routeLink(from, to, type, rtl, rowHeight) {
|
|
12937
|
+
const fwd = rtl ? -1 : 1;
|
|
12938
|
+
const fromFinish = type === 'FS' || type === 'FF';
|
|
12939
|
+
const toStart = type === 'FS' || type === 'SS';
|
|
12940
|
+
const x1 = from.shape === 'milestone' ? from.pointX : fromFinish ? from.endX : from.startX;
|
|
12941
|
+
const x2 = to.shape === 'milestone' ? to.pointX : toStart ? to.startX : to.endX;
|
|
12942
|
+
const y1 = from.midY;
|
|
12943
|
+
const y2 = to.midY;
|
|
12944
|
+
const d1 = (fromFinish ? 1 : -1) * fwd; // exit direction
|
|
12945
|
+
const d2 = (toStart ? 1 : -1) * fwd; // approach direction
|
|
12946
|
+
const exit = x1 + LINK_STUB * d1;
|
|
12947
|
+
const entry = x2 - LINK_STUB * d2;
|
|
12948
|
+
if ((entry - exit) * d2 >= 0) {
|
|
12949
|
+
return { path: `M ${x1} ${y1} H ${exit} V ${y2} H ${x2}`, badgeX: exit, badgeY: (y1 + y2) / 2 };
|
|
12950
|
+
}
|
|
12951
|
+
// Distinct rows always differ in y; the guard only keeps a degenerate input off the bar.
|
|
12952
|
+
const yMid = y1 === y2 ? y1 - rowHeight / 2 : (y1 + y2) / 2;
|
|
12953
|
+
return {
|
|
12954
|
+
path: `M ${x1} ${y1} H ${exit} V ${yMid} H ${entry} V ${y2} H ${x2}`,
|
|
12955
|
+
badgeX: entry,
|
|
12956
|
+
badgeY: yMid,
|
|
12957
|
+
};
|
|
12958
|
+
}
|
|
12749
12959
|
// ── RTL coordinate mapping ───────────────────────────────────────────────────
|
|
12750
12960
|
/** Mirror a logical x into render space. LTR is identity; RTL reflects about the canvas width. */
|
|
12751
12961
|
function mapX(logicalX, innerWidth, rtl) {
|
|
@@ -12846,6 +13056,257 @@ function weekendBands(domain, zoom, pxPerDay) {
|
|
|
12846
13056
|
return bands;
|
|
12847
13057
|
}
|
|
12848
13058
|
|
|
13059
|
+
/** Label-pane width bounds (px) the splitter clamps to. */
|
|
13060
|
+
const GANTT_LABEL_W_MIN = 140;
|
|
13061
|
+
const GANTT_LABEL_W_MAX = 720;
|
|
13062
|
+
/** Keyboard resize steps (plain arrow / with Shift). */
|
|
13063
|
+
const LABEL_STEP = 16;
|
|
13064
|
+
const LABEL_STEP_COARSE = 64;
|
|
13065
|
+
/** Clamp any candidate label-pane width into the splitter's range. */
|
|
13066
|
+
function clampGanttLabelWidth(px) {
|
|
13067
|
+
return Math.max(GANTT_LABEL_W_MIN, Math.min(GANTT_LABEL_W_MAX, Math.round(px)));
|
|
13068
|
+
}
|
|
13069
|
+
/**
|
|
13070
|
+
* The Gantt's label-pane divider: width state plus the pointer and keyboard resize gestures.
|
|
13071
|
+
*
|
|
13072
|
+
* ## Why this is not in the component
|
|
13073
|
+
* `fly-gantt` is a large component by nature — it owns a two-pane layout, a time scale, four row
|
|
13074
|
+
* shapes, drag/resize/link gestures and dependency routing. The pane divider is the one region
|
|
13075
|
+
* with *no* coupling to any of that: it needs a direction, a fallback width and somewhere to
|
|
13076
|
+
* report a committed value, and nothing else. Keeping it here means the component's remaining
|
|
13077
|
+
* bulk is all chart, and the divider's RTL rules — which are the subtle part — are stated in one
|
|
13078
|
+
* place and unit-testable without rendering an SVG.
|
|
13079
|
+
*
|
|
13080
|
+
* ## The RTL rule, once
|
|
13081
|
+
* The label pane sits on the inline-START side in both directions, so "drag outward = wider"
|
|
13082
|
+
* flips physical sign with direction, and the arrow key that widens is the one pointing AWAY
|
|
13083
|
+
* from the pane (`ArrowRight` in LTR, `ArrowLeft` in RTL). Both facts are encoded here rather
|
|
13084
|
+
* than at each call site, which is what stops one of the two gestures being mirrored and the
|
|
13085
|
+
* other not — the failure this consolidation exists to prevent.
|
|
13086
|
+
*/
|
|
13087
|
+
class GanttLabelPane {
|
|
13088
|
+
deps;
|
|
13089
|
+
/** True for the duration of a pointer drag — the host paints a resizing affordance. */
|
|
13090
|
+
resizing = signal(false, ...(ngDevMode ? [{ debugName: "resizing" }] : /* istanbul ignore next */ []));
|
|
13091
|
+
/** The user's dragged width, or `null` while the consumer's input is in charge. */
|
|
13092
|
+
_override = signal(null, ...(ngDevMode ? [{ debugName: "_override" }] : /* istanbul ignore next */ []));
|
|
13093
|
+
/** The width to render: the user's drag when present, else the clamped input. */
|
|
13094
|
+
width = computed(() => clampGanttLabelWidth(this._override() ?? this.deps.inputWidth()), ...(ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []));
|
|
13095
|
+
constructor(deps) {
|
|
13096
|
+
this.deps = deps;
|
|
13097
|
+
}
|
|
13098
|
+
/**
|
|
13099
|
+
* Pointer-drag the divider. Forward-in-time is `+x` LTR and `-x` RTL, and the label pane sits
|
|
13100
|
+
* on the inline-start side in both, so the same sign flip that mirrors the timeline also
|
|
13101
|
+
* mirrors "drag outward = wider".
|
|
13102
|
+
*
|
|
13103
|
+
* Listeners go on `document`, not the divider: a fast drag outruns the 8px-wide handle, and a
|
|
13104
|
+
* pointer that leaves it would otherwise strand the gesture mid-resize.
|
|
13105
|
+
*/
|
|
13106
|
+
onPointerDown(ev) {
|
|
13107
|
+
if (!this.deps.resizable() || ev.button !== 0)
|
|
13108
|
+
return;
|
|
13109
|
+
ev.preventDefault();
|
|
13110
|
+
ev.stopPropagation();
|
|
13111
|
+
const startClientX = ev.clientX;
|
|
13112
|
+
const startWidth = this.width();
|
|
13113
|
+
const dirSign = this.deps.rtl() ? -1 : 1;
|
|
13114
|
+
this.resizing.set(true);
|
|
13115
|
+
const onMove = (e) => {
|
|
13116
|
+
this._override.set(clampGanttLabelWidth(startWidth + (e.clientX - startClientX) * dirSign));
|
|
13117
|
+
};
|
|
13118
|
+
const onUp = () => {
|
|
13119
|
+
document.removeEventListener('pointermove', onMove);
|
|
13120
|
+
document.removeEventListener('pointerup', onUp);
|
|
13121
|
+
this.resizing.set(false);
|
|
13122
|
+
const final = this.width();
|
|
13123
|
+
// Only a width that actually MOVED is committed: a plain click on the divider must not
|
|
13124
|
+
// emit, or a consumer persisting the value would write on every stray press.
|
|
13125
|
+
if (final !== startWidth)
|
|
13126
|
+
this.deps.commit(final);
|
|
13127
|
+
};
|
|
13128
|
+
document.addEventListener('pointermove', onMove);
|
|
13129
|
+
document.addEventListener('pointerup', onUp);
|
|
13130
|
+
this.deps.destroyRef.onDestroy(() => {
|
|
13131
|
+
document.removeEventListener('pointermove', onMove);
|
|
13132
|
+
document.removeEventListener('pointerup', onUp);
|
|
13133
|
+
});
|
|
13134
|
+
}
|
|
13135
|
+
/**
|
|
13136
|
+
* Keyboard resize on the focused divider. ArrowRight/Left grow/shrink by the *inline* meaning
|
|
13137
|
+
* of the key, so under RTL the arrow that points away from the pane is still the one that
|
|
13138
|
+
* widens it. `Home`/`End` jump to the bounds, Enter/Escape restore the input width.
|
|
13139
|
+
*
|
|
13140
|
+
* Typed `Event` because Angular's strict template checker types a `(keydown)` `$event` as
|
|
13141
|
+
* `Event`; the cast is done here once rather than at the binding.
|
|
13142
|
+
*/
|
|
13143
|
+
onKeydown(ev) {
|
|
13144
|
+
if (!this.deps.resizable())
|
|
13145
|
+
return;
|
|
13146
|
+
const key = ev.key;
|
|
13147
|
+
const coarse = ev.shiftKey;
|
|
13148
|
+
const step = coarse ? LABEL_STEP_COARSE : LABEL_STEP;
|
|
13149
|
+
const rtl = this.deps.rtl();
|
|
13150
|
+
const grow = rtl ? 'ArrowLeft' : 'ArrowRight';
|
|
13151
|
+
const shrink = rtl ? 'ArrowRight' : 'ArrowLeft';
|
|
13152
|
+
let next;
|
|
13153
|
+
if (key === grow)
|
|
13154
|
+
next = this.width() + step;
|
|
13155
|
+
else if (key === shrink)
|
|
13156
|
+
next = this.width() - step;
|
|
13157
|
+
else if (key === 'Home')
|
|
13158
|
+
next = GANTT_LABEL_W_MIN;
|
|
13159
|
+
else if (key === 'End')
|
|
13160
|
+
next = GANTT_LABEL_W_MAX;
|
|
13161
|
+
else if (key === 'Enter' || key === 'Escape') {
|
|
13162
|
+
// Stop the grid-level Enter handler from also activating the selected row.
|
|
13163
|
+
ev.preventDefault();
|
|
13164
|
+
ev.stopPropagation();
|
|
13165
|
+
this.reset();
|
|
13166
|
+
return;
|
|
13167
|
+
}
|
|
13168
|
+
else
|
|
13169
|
+
return;
|
|
13170
|
+
ev.preventDefault();
|
|
13171
|
+
ev.stopPropagation();
|
|
13172
|
+
const clamped = clampGanttLabelWidth(next);
|
|
13173
|
+
// Already at a bound: swallow the key (it was ours) but emit nothing.
|
|
13174
|
+
if (clamped === this.width())
|
|
13175
|
+
return;
|
|
13176
|
+
this._override.set(clamped);
|
|
13177
|
+
this.deps.commit(clamped);
|
|
13178
|
+
}
|
|
13179
|
+
/** Drop the user's dragged width and fall back to the consumer's input width. */
|
|
13180
|
+
reset() {
|
|
13181
|
+
if (this._override() === null)
|
|
13182
|
+
return;
|
|
13183
|
+
this._override.set(null);
|
|
13184
|
+
this.deps.commit(this.width());
|
|
13185
|
+
}
|
|
13186
|
+
}
|
|
13187
|
+
|
|
13188
|
+
/**
|
|
13189
|
+
* Drawing a dependency by dragging from one end of a bar to another — the gesture, the drop-target
|
|
13190
|
+
* resolution and the resulting relationship type.
|
|
13191
|
+
*
|
|
13192
|
+
* ## Why this is not in the component
|
|
13193
|
+
* `fly-gantt` already owns a two-pane layout, a time scale, four row shapes and bar move/resize.
|
|
13194
|
+
* This is a self-contained second gesture with one output, and its interesting part is a *rule*
|
|
13195
|
+
* rather than any rendering: which end you START on plus which end you DROP nearest decides which
|
|
13196
|
+
* of the four MS-Project relationships you get. Stating that rule beside the hit-testing that
|
|
13197
|
+
* feeds it — rather than in the middle of a 700-line component — is what makes it reviewable.
|
|
13198
|
+
*
|
|
13199
|
+
* ## The selection is here too, and deliberately
|
|
13200
|
+
* `selectedKey` is the link the user has clicked, which is what the delete affordance and the
|
|
13201
|
+
* keyboard's Delete both act on. It lives with the gesture because both are "the user is
|
|
13202
|
+
* manipulating dependencies", and keeping them together stops a click-selection surviving a drag
|
|
13203
|
+
* that replaced the very link it pointed at.
|
|
13204
|
+
*/
|
|
13205
|
+
class GanttLinkGestures {
|
|
13206
|
+
deps;
|
|
13207
|
+
/** The in-flight drag, or `null`. */
|
|
13208
|
+
gesture = signal(null, ...(ngDevMode ? [{ debugName: "gesture" }] : /* istanbul ignore next */ []));
|
|
13209
|
+
/** The clicked link's key, or `null`. Drives the delete affordance and the Delete key. */
|
|
13210
|
+
selectedKey = signal(null, ...(ngDevMode ? [{ debugName: "selectedKey" }] : /* istanbul ignore next */ []));
|
|
13211
|
+
/** Rubber-band path for the in-flight gesture (render space), or `null` when idle. */
|
|
13212
|
+
path = computed(() => {
|
|
13213
|
+
const g = this.gesture();
|
|
13214
|
+
return g ? `M ${g.x0} ${g.y0} L ${g.x1} ${g.y1}` : null;
|
|
13215
|
+
}, ...(ngDevMode ? [{ debugName: "path" }] : /* istanbul ignore next */ []));
|
|
13216
|
+
constructor(deps) {
|
|
13217
|
+
this.deps = deps;
|
|
13218
|
+
}
|
|
13219
|
+
/** Clear any link selection — the host calls this when the user selects a row instead. */
|
|
13220
|
+
clearSelection() {
|
|
13221
|
+
this.selectedKey.set(null);
|
|
13222
|
+
}
|
|
13223
|
+
/** Toggle the clicked link's selection. */
|
|
13224
|
+
toggleSelection(key) {
|
|
13225
|
+
this.selectedKey.set(this.selectedKey() === key ? null : key);
|
|
13226
|
+
}
|
|
13227
|
+
/**
|
|
13228
|
+
* Start a link gesture from one end of a bar. Which end it started on, plus which end of the
|
|
13229
|
+
* target row it is dropped nearest to, is what selects the dependency type — so all four
|
|
13230
|
+
* MS-Project relationships are reachable with the same single drag.
|
|
13231
|
+
*
|
|
13232
|
+
* Listeners go on `document`, not the connector: the connector is an 8px dot and any real drag
|
|
13233
|
+
* leaves it immediately.
|
|
13234
|
+
*/
|
|
13235
|
+
start(ev, row, anchor) {
|
|
13236
|
+
if (this.deps.readonly() || row.flat.row.readonly || ev.button !== 0)
|
|
13237
|
+
return;
|
|
13238
|
+
ev.preventDefault();
|
|
13239
|
+
ev.stopPropagation();
|
|
13240
|
+
const local = this.toBodyPoint(ev.clientX, ev.clientY);
|
|
13241
|
+
const x0 = row.shape === 'milestone' ? row.pointX : anchor === 'finish' ? row.endX : row.startX;
|
|
13242
|
+
this.gesture.set({
|
|
13243
|
+
fromId: row.flat.row.id,
|
|
13244
|
+
anchor,
|
|
13245
|
+
x0,
|
|
13246
|
+
y0: row.midY,
|
|
13247
|
+
x1: local.x,
|
|
13248
|
+
y1: local.y,
|
|
13249
|
+
});
|
|
13250
|
+
const onMove = (e) => {
|
|
13251
|
+
const p = this.toBodyPoint(e.clientX, e.clientY);
|
|
13252
|
+
const g = this.gesture();
|
|
13253
|
+
if (g)
|
|
13254
|
+
this.gesture.set({ ...g, x1: p.x, y1: p.y });
|
|
13255
|
+
};
|
|
13256
|
+
const onUp = (e) => {
|
|
13257
|
+
document.removeEventListener('pointermove', onMove);
|
|
13258
|
+
document.removeEventListener('pointerup', onUp);
|
|
13259
|
+
const g = this.gesture();
|
|
13260
|
+
this.gesture.set(null);
|
|
13261
|
+
if (!g)
|
|
13262
|
+
return;
|
|
13263
|
+
const target = this.dropTargetAt(e.clientX, e.clientY);
|
|
13264
|
+
// A row cannot depend on itself; a drop on empty space is simply abandoned.
|
|
13265
|
+
if (target && target.id !== g.fromId) {
|
|
13266
|
+
this.deps.createLink({
|
|
13267
|
+
fromId: g.fromId,
|
|
13268
|
+
toId: target.id,
|
|
13269
|
+
type: linkTypeFor(g.anchor, target.anchor),
|
|
13270
|
+
});
|
|
13271
|
+
}
|
|
13272
|
+
};
|
|
13273
|
+
document.addEventListener('pointermove', onMove);
|
|
13274
|
+
document.addEventListener('pointerup', onUp);
|
|
13275
|
+
this.deps.destroyRef.onDestroy(() => {
|
|
13276
|
+
document.removeEventListener('pointermove', onMove);
|
|
13277
|
+
document.removeEventListener('pointerup', onUp);
|
|
13278
|
+
});
|
|
13279
|
+
}
|
|
13280
|
+
/** Convert client coordinates to a point inside the body SVG (render space). */
|
|
13281
|
+
toBodyPoint(clientX, clientY) {
|
|
13282
|
+
const svg = this.deps.bodySvg();
|
|
13283
|
+
if (!svg)
|
|
13284
|
+
return { x: 0, y: 0 };
|
|
13285
|
+
const rect = svg.getBoundingClientRect();
|
|
13286
|
+
return { x: clientX - rect.left, y: clientY - rect.top };
|
|
13287
|
+
}
|
|
13288
|
+
/**
|
|
13289
|
+
* The row a link gesture was dropped on **and which of its ends** the drop landed nearest —
|
|
13290
|
+
* the second half of the type derivation. A milestone has no width, so both of its ends are the
|
|
13291
|
+
* same point and it is reported as `start`, giving the FS/SS pair anyone actually wants.
|
|
13292
|
+
*/
|
|
13293
|
+
dropTargetAt(clientX, clientY) {
|
|
13294
|
+
const el = document.elementFromPoint(clientX, clientY);
|
|
13295
|
+
const holder = el?.closest('[data-row-id]');
|
|
13296
|
+
const id = holder?.getAttribute('data-row-id');
|
|
13297
|
+
if (!id)
|
|
13298
|
+
return null;
|
|
13299
|
+
const row = this.deps.rows().find((r) => r.flat.row.id === id);
|
|
13300
|
+
if (!row || row.shape === 'milestone' || row.shape === 'empty')
|
|
13301
|
+
return { id, anchor: 'start' };
|
|
13302
|
+
const x = this.toBodyPoint(clientX, clientY).x;
|
|
13303
|
+
return {
|
|
13304
|
+
id,
|
|
13305
|
+
anchor: Math.abs(x - row.endX) < Math.abs(x - row.startX) ? 'finish' : 'start',
|
|
13306
|
+
};
|
|
13307
|
+
}
|
|
13308
|
+
}
|
|
13309
|
+
|
|
12849
13310
|
/** Process-wide counter so each instance owns a unique arrowhead `<marker>` id. */
|
|
12850
13311
|
let _flyGanttUid = 0;
|
|
12851
13312
|
/** Header band heights (two-tier: coarse period over fine period). */
|
|
@@ -12856,27 +13317,6 @@ const HEADER_H = HEADER_UPPER_H + HEADER_LOWER_H;
|
|
|
12856
13317
|
const BAR_V_PAD = 6;
|
|
12857
13318
|
/** Edge-handle hit width (px) for resize + the link connector radius. */
|
|
12858
13319
|
const HANDLE_W = 8;
|
|
12859
|
-
/** Label-pane width bounds (px) the splitter clamps to. */
|
|
12860
|
-
const MIN_LABEL_W = 140;
|
|
12861
|
-
const MAX_LABEL_W = 720;
|
|
12862
|
-
/** Keyboard resize steps (plain arrow / with Shift). */
|
|
12863
|
-
const LABEL_STEP = 16;
|
|
12864
|
-
const LABEL_STEP_COARSE = 64;
|
|
12865
|
-
/** Straight run (px) a dependency elbow travels out of a bar edge before it turns. */
|
|
12866
|
-
const LINK_STUB = 12;
|
|
12867
|
-
/** Clamp any candidate label-pane width into the splitter's range. */
|
|
12868
|
-
function clampLabelWidth(px) {
|
|
12869
|
-
return Math.max(MIN_LABEL_W, Math.min(MAX_LABEL_W, Math.round(px)));
|
|
12870
|
-
}
|
|
12871
|
-
/**
|
|
12872
|
-
* Relationship kind implied by the edge a gesture left and the edge it landed on. The name is
|
|
12873
|
-
* literally `<predecessor endpoint><successor endpoint>`, so this is a lookup, not a rule.
|
|
12874
|
-
*/
|
|
12875
|
-
function linkTypeFor(from, to) {
|
|
12876
|
-
if (from === 'finish')
|
|
12877
|
-
return to === 'start' ? 'FS' : 'FF';
|
|
12878
|
-
return to === 'start' ? 'SS' : 'SF';
|
|
12879
|
-
}
|
|
12880
13320
|
/**
|
|
12881
13321
|
* **`fly-gantt`** — the design-system SVG Gantt chart.
|
|
12882
13322
|
*
|
|
@@ -12948,22 +13388,16 @@ class FlyGanttComponent {
|
|
|
12948
13388
|
HEADER_UPPER_H = HEADER_UPPER_H;
|
|
12949
13389
|
HEADER_LOWER_H = HEADER_LOWER_H;
|
|
12950
13390
|
HANDLE_W = HANDLE_W;
|
|
12951
|
-
MIN_LABEL_W =
|
|
12952
|
-
MAX_LABEL_W =
|
|
13391
|
+
MIN_LABEL_W = GANTT_LABEL_W_MIN;
|
|
13392
|
+
MAX_LABEL_W = GANTT_LABEL_W_MAX;
|
|
12953
13393
|
// ── Interaction state ─────────────────────────────────────────────────────────
|
|
12954
13394
|
selectedId = signal(null, ...(ngDevMode ? [{ debugName: "selectedId" }] : /* istanbul ignore next */ []));
|
|
12955
|
-
/** Key (`from~to~type`) of the dependency arrow the user has selected, if any. */
|
|
12956
|
-
selectedLinkKey = signal(null, ...(ngDevMode ? [{ debugName: "selectedLinkKey" }] : /* istanbul ignore next */ []));
|
|
12957
13395
|
_collapsed = signal(new Set(), ...(ngDevMode ? [{ debugName: "_collapsed" }] : /* istanbul ignore next */ []));
|
|
12958
13396
|
/** Live drag preview `{id,start,end}` folded into geometry while a gesture runs. */
|
|
12959
13397
|
_dragPreview = signal(null, ...(ngDevMode ? [{ debugName: "_dragPreview" }] : /* istanbul ignore next */ []));
|
|
12960
|
-
/** Rubber-band link gesture render-space anchor + cursor, and its origin row + edge. */
|
|
12961
|
-
linkGesture = signal(null, ...(ngDevMode ? [{ debugName: "linkGesture" }] : /* istanbul ignore next */ []));
|
|
12962
13398
|
dragTooltip = signal(null, ...(ngDevMode ? [{ debugName: "dragTooltip" }] : /* istanbul ignore next */ []));
|
|
12963
13399
|
/** True while a divider drag is in flight (suppresses text selection host-wide). */
|
|
12964
|
-
resizingLabels = signal(false, ...(ngDevMode ? [{ debugName: "resizingLabels" }] : /* istanbul ignore next */ []));
|
|
12965
13400
|
/** User-chosen pane width; `null` = still following the {@link labelWidth} input. */
|
|
12966
|
-
_labelWidthOverride = signal(null, ...(ngDevMode ? [{ debugName: "_labelWidthOverride" }] : /* istanbul ignore next */ []));
|
|
12967
13401
|
bodyRef = viewChild('bodySvg', ...(ngDevMode ? [{ debugName: "bodyRef" }] : /* istanbul ignore next */ []));
|
|
12968
13402
|
// ── Derived model ──────────────────────────────────────────────────────────────
|
|
12969
13403
|
rtl = computed(() => this.i18n.isRtl(), ...(ngDevMode ? [{ debugName: "rtl" }] : /* istanbul ignore next */ []));
|
|
@@ -12971,7 +13405,6 @@ class FlyGanttComponent {
|
|
|
12971
13405
|
domain = computed(() => computeDomain(this.rows(), this.zoom()), ...(ngDevMode ? [{ debugName: "domain" }] : /* istanbul ignore next */ []));
|
|
12972
13406
|
innerWidth = computed(() => Math.max(1, dateToX(this.domain().end, this.domain().start, this.pxPerDay())), ...(ngDevMode ? [{ debugName: "innerWidth" }] : /* istanbul ignore next */ []));
|
|
12973
13407
|
/** Label-pane width actually rendered — the user's dragged width, else the input. */
|
|
12974
|
-
effectiveLabelWidth = computed(() => clampLabelWidth(this._labelWidthOverride() ?? this.labelWidth()), ...(ngDevMode ? [{ debugName: "effectiveLabelWidth" }] : /* istanbul ignore next */ []));
|
|
12975
13408
|
/** Full visible list after tree flatten/collapse. */
|
|
12976
13409
|
_flat = computed(() => flattenRows(this.rows(), this._collapsed()), ...(ngDevMode ? [{ debugName: "_flat" }] : /* istanbul ignore next */ []));
|
|
12977
13410
|
/** Capped list actually rendered. */
|
|
@@ -13073,7 +13506,7 @@ class FlyGanttComponent {
|
|
|
13073
13506
|
const to = vmById.get(link.toId);
|
|
13074
13507
|
if (!from || !to)
|
|
13075
13508
|
continue;
|
|
13076
|
-
const routed =
|
|
13509
|
+
const routed = routeLink(from, to, link.type, rtl, this.rowHeight());
|
|
13077
13510
|
out.push({
|
|
13078
13511
|
key: `${link.fromId}~${link.toId}~${link.type}`,
|
|
13079
13512
|
fromId: link.fromId,
|
|
@@ -13129,85 +13562,31 @@ class FlyGanttComponent {
|
|
|
13129
13562
|
}
|
|
13130
13563
|
// ── Label-pane divider ─────────────────────────────────────────────────────────
|
|
13131
13564
|
/**
|
|
13132
|
-
*
|
|
13133
|
-
*
|
|
13134
|
-
*
|
|
13565
|
+
* The divider's own state machine, in {@link GanttLabelPane}. It is composed rather than
|
|
13566
|
+
* inlined because it is the one region of this component with no coupling to the chart: give it
|
|
13567
|
+
* a direction, a fallback width and somewhere to report a committed value and it is complete.
|
|
13568
|
+
* The subtle part — that "wider" flips physical sign under RTL for BOTH the drag and the arrow
|
|
13569
|
+
* keys — is stated once there instead of twice here.
|
|
13135
13570
|
*/
|
|
13571
|
+
_labelPane = new GanttLabelPane({
|
|
13572
|
+
resizable: this.resizableLabels,
|
|
13573
|
+
rtl: this.rtl,
|
|
13574
|
+
inputWidth: this.labelWidth,
|
|
13575
|
+
commit: (width) => this.labelWidthChange.emit(width),
|
|
13576
|
+
destroyRef: this.destroyRef,
|
|
13577
|
+
});
|
|
13578
|
+
/** Painted width of the label pane — the user's dragged value, else the clamped input. */
|
|
13579
|
+
effectiveLabelWidth = this._labelPane.width;
|
|
13580
|
+
/** True mid-drag; drives the host's resizing cursor/affordance. */
|
|
13581
|
+
resizingLabels = this._labelPane.resizing;
|
|
13136
13582
|
onLabelResizePointerDown(ev) {
|
|
13137
|
-
|
|
13138
|
-
return;
|
|
13139
|
-
ev.preventDefault();
|
|
13140
|
-
ev.stopPropagation();
|
|
13141
|
-
const startClientX = ev.clientX;
|
|
13142
|
-
const startWidth = this.effectiveLabelWidth();
|
|
13143
|
-
const dirSign = this.rtl() ? -1 : 1;
|
|
13144
|
-
this.resizingLabels.set(true);
|
|
13145
|
-
const onMove = (e) => {
|
|
13146
|
-
this._labelWidthOverride.set(clampLabelWidth(startWidth + (e.clientX - startClientX) * dirSign));
|
|
13147
|
-
};
|
|
13148
|
-
const onUp = () => {
|
|
13149
|
-
document.removeEventListener('pointermove', onMove);
|
|
13150
|
-
document.removeEventListener('pointerup', onUp);
|
|
13151
|
-
this.resizingLabels.set(false);
|
|
13152
|
-
const final = this.effectiveLabelWidth();
|
|
13153
|
-
if (final !== startWidth)
|
|
13154
|
-
this.labelWidthChange.emit(final);
|
|
13155
|
-
};
|
|
13156
|
-
document.addEventListener('pointermove', onMove);
|
|
13157
|
-
document.addEventListener('pointerup', onUp);
|
|
13158
|
-
this.destroyRef.onDestroy(() => {
|
|
13159
|
-
document.removeEventListener('pointermove', onMove);
|
|
13160
|
-
document.removeEventListener('pointerup', onUp);
|
|
13161
|
-
});
|
|
13583
|
+
this._labelPane.onPointerDown(ev);
|
|
13162
13584
|
}
|
|
13163
|
-
/**
|
|
13164
|
-
* Keyboard resize on the focused divider. ArrowRight/Left grow/shrink by the *inline* meaning
|
|
13165
|
-
* of the key, so under RTL the arrow that points away from the pane is still the one that
|
|
13166
|
-
* widens it. `Home`/`End` jump to the bounds, Enter/Escape restore the input width.
|
|
13167
|
-
*
|
|
13168
|
-
* Typed `Event` for the same reason as {@link onLabelRowKeydown}: Angular's strict template
|
|
13169
|
-
* checker types `(keydown)` `$event` as `Event`, so the cast is done here once.
|
|
13170
|
-
*/
|
|
13171
13585
|
onLabelResizeKeydown(ev) {
|
|
13172
|
-
|
|
13173
|
-
return;
|
|
13174
|
-
const key = ev.key;
|
|
13175
|
-
const coarse = ev.shiftKey;
|
|
13176
|
-
const step = coarse ? LABEL_STEP_COARSE : LABEL_STEP;
|
|
13177
|
-
const grow = this.rtl() ? 'ArrowLeft' : 'ArrowRight';
|
|
13178
|
-
const shrink = this.rtl() ? 'ArrowRight' : 'ArrowLeft';
|
|
13179
|
-
let next;
|
|
13180
|
-
if (key === grow)
|
|
13181
|
-
next = this.effectiveLabelWidth() + step;
|
|
13182
|
-
else if (key === shrink)
|
|
13183
|
-
next = this.effectiveLabelWidth() - step;
|
|
13184
|
-
else if (key === 'Home')
|
|
13185
|
-
next = MIN_LABEL_W;
|
|
13186
|
-
else if (key === 'End')
|
|
13187
|
-
next = MAX_LABEL_W;
|
|
13188
|
-
else if (key === 'Enter' || key === 'Escape') {
|
|
13189
|
-
// Stop the grid-level Enter handler from also activating the selected row.
|
|
13190
|
-
ev.preventDefault();
|
|
13191
|
-
ev.stopPropagation();
|
|
13192
|
-
this.resetLabelWidth();
|
|
13193
|
-
return;
|
|
13194
|
-
}
|
|
13195
|
-
else
|
|
13196
|
-
return;
|
|
13197
|
-
ev.preventDefault();
|
|
13198
|
-
ev.stopPropagation();
|
|
13199
|
-
const clamped = clampLabelWidth(next);
|
|
13200
|
-
if (clamped === this.effectiveLabelWidth())
|
|
13201
|
-
return;
|
|
13202
|
-
this._labelWidthOverride.set(clamped);
|
|
13203
|
-
this.labelWidthChange.emit(clamped);
|
|
13586
|
+
this._labelPane.onKeydown(ev);
|
|
13204
13587
|
}
|
|
13205
|
-
/** Drop the user's dragged width and fall back to the {@link labelWidth} input. */
|
|
13206
13588
|
resetLabelWidth() {
|
|
13207
|
-
|
|
13208
|
-
return;
|
|
13209
|
-
this._labelWidthOverride.set(null);
|
|
13210
|
-
this.labelWidthChange.emit(this.effectiveLabelWidth());
|
|
13589
|
+
this._labelPane.reset();
|
|
13211
13590
|
}
|
|
13212
13591
|
// ── Group collapse ─────────────────────────────────────────────────────────────
|
|
13213
13592
|
isCollapsed(id) {
|
|
@@ -13238,7 +13617,7 @@ class FlyGanttComponent {
|
|
|
13238
13617
|
this.selectedId.set(id);
|
|
13239
13618
|
// A row and a link are alternative selections — picking one drops the other, so Delete is
|
|
13240
13619
|
// never ambiguous about which thing it would remove.
|
|
13241
|
-
this.
|
|
13620
|
+
this._links.clearSelection();
|
|
13242
13621
|
this.rowClick.emit(id);
|
|
13243
13622
|
}
|
|
13244
13623
|
onRowDblClick(id) {
|
|
@@ -13247,14 +13626,14 @@ class FlyGanttComponent {
|
|
|
13247
13626
|
// ── Dependency arrow selection + removal ───────────────────────────────────────
|
|
13248
13627
|
onLinkClick(ev, vm) {
|
|
13249
13628
|
ev.stopPropagation();
|
|
13250
|
-
this.
|
|
13629
|
+
this._links.toggleSelection(vm.key);
|
|
13251
13630
|
}
|
|
13252
13631
|
deleteLink(ev, vm) {
|
|
13253
13632
|
ev.stopPropagation();
|
|
13254
13633
|
ev.preventDefault();
|
|
13255
13634
|
if (this.readonly())
|
|
13256
13635
|
return;
|
|
13257
|
-
this.
|
|
13636
|
+
this._links.clearSelection();
|
|
13258
13637
|
this.dependencyDelete.emit({ fromId: vm.fromId, toId: vm.toId, type: vm.type });
|
|
13259
13638
|
}
|
|
13260
13639
|
// ── Keyboard ───────────────────────────────────────────────────────────────────
|
|
@@ -13391,54 +13770,27 @@ class FlyGanttComponent {
|
|
|
13391
13770
|
}
|
|
13392
13771
|
// ── Pointer drag: dependency create ──────────────────────────────────────────────
|
|
13393
13772
|
/**
|
|
13394
|
-
*
|
|
13395
|
-
*
|
|
13396
|
-
*
|
|
13773
|
+
* The dependency-drawing gesture, in {@link GanttLinkGestures}. Composed rather than inlined
|
|
13774
|
+
* because it is a self-contained second gesture whose interesting part is a RULE — the end you
|
|
13775
|
+
* start on plus the end you drop nearest, selects the relationship type — which reads far better beside
|
|
13776
|
+
* its own hit-testing than in the middle of this component.
|
|
13397
13777
|
*/
|
|
13778
|
+
_links = new GanttLinkGestures({
|
|
13779
|
+
readonly: this.readonly,
|
|
13780
|
+
rows: this.rowVms,
|
|
13781
|
+
bodySvg: () => this.bodyRef()?.nativeElement ?? null,
|
|
13782
|
+
createLink: (dependency) => this.dependencyCreate.emit(dependency),
|
|
13783
|
+
destroyRef: this.destroyRef,
|
|
13784
|
+
});
|
|
13785
|
+
/** Key (`from~to~type`) of the dependency arrow the user has selected, if any. */
|
|
13786
|
+
selectedLinkKey = this._links.selectedKey;
|
|
13787
|
+
/** The in-flight rubber-band gesture, or `null` — the template draws it when present. */
|
|
13788
|
+
linkGesture = this._links.gesture;
|
|
13789
|
+
/** Rubber-band path for an in-flight link gesture (render space). */
|
|
13790
|
+
linkGesturePath = this._links.path;
|
|
13398
13791
|
onLinkPointerDown(ev, vm, anchor) {
|
|
13399
|
-
|
|
13400
|
-
return;
|
|
13401
|
-
ev.preventDefault();
|
|
13402
|
-
ev.stopPropagation();
|
|
13403
|
-
const local = this._toBodyPoint(ev);
|
|
13404
|
-
const x0 = vm.shape === 'milestone' ? vm.pointX : anchor === 'finish' ? vm.endX : vm.startX;
|
|
13405
|
-
this.linkGesture.set({ fromId: vm.flat.row.id, anchor, x0, y0: vm.midY, x1: local.x, y1: local.y });
|
|
13406
|
-
const onMove = (e) => {
|
|
13407
|
-
const p = this._toBodyPoint(e);
|
|
13408
|
-
const g = this.linkGesture();
|
|
13409
|
-
if (g)
|
|
13410
|
-
this.linkGesture.set({ ...g, x1: p.x, y1: p.y });
|
|
13411
|
-
};
|
|
13412
|
-
const onUp = (e) => {
|
|
13413
|
-
document.removeEventListener('pointermove', onMove);
|
|
13414
|
-
document.removeEventListener('pointerup', onUp);
|
|
13415
|
-
const g = this.linkGesture();
|
|
13416
|
-
this.linkGesture.set(null);
|
|
13417
|
-
if (!g)
|
|
13418
|
-
return;
|
|
13419
|
-
const target = this._dropTargetAt(e.clientX, e.clientY);
|
|
13420
|
-
if (target && target.id !== g.fromId) {
|
|
13421
|
-
this.dependencyCreate.emit({
|
|
13422
|
-
fromId: g.fromId,
|
|
13423
|
-
toId: target.id,
|
|
13424
|
-
type: linkTypeFor(g.anchor, target.anchor),
|
|
13425
|
-
});
|
|
13426
|
-
}
|
|
13427
|
-
};
|
|
13428
|
-
document.addEventListener('pointermove', onMove);
|
|
13429
|
-
document.addEventListener('pointerup', onUp);
|
|
13430
|
-
this.destroyRef.onDestroy(() => {
|
|
13431
|
-
document.removeEventListener('pointermove', onMove);
|
|
13432
|
-
document.removeEventListener('pointerup', onUp);
|
|
13433
|
-
});
|
|
13792
|
+
this._links.start(ev, vm, anchor);
|
|
13434
13793
|
}
|
|
13435
|
-
/** Rubber-band path for an in-flight link gesture (render space). */
|
|
13436
|
-
linkGesturePath = computed(() => {
|
|
13437
|
-
const g = this.linkGesture();
|
|
13438
|
-
if (!g)
|
|
13439
|
-
return null;
|
|
13440
|
-
return `M ${g.x0} ${g.y0} L ${g.x1} ${g.y1}`;
|
|
13441
|
-
}, ...(ngDevMode ? [{ debugName: "linkGesturePath" }] : /* istanbul ignore next */ []));
|
|
13442
13794
|
// ── Internals ────────────────────────────────────────────────────────────────────
|
|
13443
13795
|
/**
|
|
13444
13796
|
* Resolve a row's `backgroundColor` into the low-alpha band colour actually painted.
|
|
@@ -13459,70 +13811,6 @@ class FlyGanttComponent {
|
|
|
13459
13811
|
// Reflect each cell: its inline-start edge becomes innerWidth - (x + width).
|
|
13460
13812
|
return ticks.map((t) => ({ ...t, x: w - t.x - t.width }));
|
|
13461
13813
|
}
|
|
13462
|
-
/**
|
|
13463
|
-
* Route a dependency elbow for any of the four relationship kinds (render space).
|
|
13464
|
-
*
|
|
13465
|
-
* The kind names the two endpoints, so it decides both the anchor x's and the direction the
|
|
13466
|
-
* arrow travels: leaving a *finish* edge moves forward in time, leaving a *start* edge moves
|
|
13467
|
-
* backward; entering a *start* edge approaches from behind, entering a *finish* edge from
|
|
13468
|
-
* ahead. `fwd` folds RTL in, so none of that branches on direction.
|
|
13469
|
-
*
|
|
13470
|
-
* One turn suffices when the entry stub lies ahead of the exit stub in the approach
|
|
13471
|
-
* direction. When it does not — overlapping or reversed bars, which SS/FF/SF hit constantly —
|
|
13472
|
-
* a straight drop would cut back through the bars, so the elbow detours via the gap between
|
|
13473
|
-
* the two rows instead.
|
|
13474
|
-
*/
|
|
13475
|
-
_route(from, to, type, rtl) {
|
|
13476
|
-
const fwd = rtl ? -1 : 1;
|
|
13477
|
-
const fromFinish = type === 'FS' || type === 'FF';
|
|
13478
|
-
const toStart = type === 'FS' || type === 'SS';
|
|
13479
|
-
const x1 = from.shape === 'milestone' ? from.pointX : fromFinish ? from.endX : from.startX;
|
|
13480
|
-
const x2 = to.shape === 'milestone' ? to.pointX : toStart ? to.startX : to.endX;
|
|
13481
|
-
const y1 = from.midY;
|
|
13482
|
-
const y2 = to.midY;
|
|
13483
|
-
const d1 = (fromFinish ? 1 : -1) * fwd; // exit direction
|
|
13484
|
-
const d2 = (toStart ? 1 : -1) * fwd; // approach direction
|
|
13485
|
-
const exit = x1 + LINK_STUB * d1;
|
|
13486
|
-
const entry = x2 - LINK_STUB * d2;
|
|
13487
|
-
if ((entry - exit) * d2 >= 0) {
|
|
13488
|
-
return { path: `M ${x1} ${y1} H ${exit} V ${y2} H ${x2}`, badgeX: exit, badgeY: (y1 + y2) / 2 };
|
|
13489
|
-
}
|
|
13490
|
-
// Distinct rows always differ in y; the guard only keeps a degenerate input off the bar.
|
|
13491
|
-
const yMid = y1 === y2 ? y1 - this.rowHeight() / 2 : (y1 + y2) / 2;
|
|
13492
|
-
return {
|
|
13493
|
-
path: `M ${x1} ${y1} H ${exit} V ${yMid} H ${entry} V ${y2} H ${x2}`,
|
|
13494
|
-
badgeX: entry,
|
|
13495
|
-
badgeY: yMid,
|
|
13496
|
-
};
|
|
13497
|
-
}
|
|
13498
|
-
/** Convert a pointer event to a coordinate inside the body SVG (render space). */
|
|
13499
|
-
_toBodyPoint(ev) {
|
|
13500
|
-
const svg = this.bodyRef()?.nativeElement;
|
|
13501
|
-
if (!svg)
|
|
13502
|
-
return { x: 0, y: 0 };
|
|
13503
|
-
const rect = svg.getBoundingClientRect();
|
|
13504
|
-
return { x: ev.clientX - rect.left, y: ev.clientY - rect.top };
|
|
13505
|
-
}
|
|
13506
|
-
/**
|
|
13507
|
-
* The row a link gesture was dropped on **and which of its ends** the drop landed nearest —
|
|
13508
|
-
* the second half of the type derivation. A milestone has no width, so both of its ends are
|
|
13509
|
-
* the same point and it is reported as `start`, giving the FS/SS pair anyone actually wants.
|
|
13510
|
-
*/
|
|
13511
|
-
_dropTargetAt(clientX, clientY) {
|
|
13512
|
-
const el = document.elementFromPoint(clientX, clientY);
|
|
13513
|
-
const holder = el?.closest('[data-row-id]');
|
|
13514
|
-
const id = holder?.getAttribute('data-row-id');
|
|
13515
|
-
if (!id)
|
|
13516
|
-
return null;
|
|
13517
|
-
const vm = this.rowVms().find((v) => v.flat.row.id === id);
|
|
13518
|
-
if (!vm || vm.shape === 'milestone' || vm.shape === 'empty')
|
|
13519
|
-
return { id, anchor: 'start' };
|
|
13520
|
-
const x = this._toBodyPoint({ clientX, clientY }).x;
|
|
13521
|
-
return {
|
|
13522
|
-
id,
|
|
13523
|
-
anchor: Math.abs(x - vm.endX) < Math.abs(x - vm.startX) ? 'finish' : 'start',
|
|
13524
|
-
};
|
|
13525
|
-
}
|
|
13526
13814
|
_ariaFor(row, start, end, shape) {
|
|
13527
13815
|
const t = (k, p) => this.i18n.t(k, p);
|
|
13528
13816
|
if (shape === 'milestone') {
|
|
@@ -13542,7 +13830,7 @@ class FlyGanttComponent {
|
|
|
13542
13830
|
: t('gantt.aria.bar', { label: row.label, start: toIsoDate(start), end: toIsoDate(end), progress });
|
|
13543
13831
|
}
|
|
13544
13832
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyGanttComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13545
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyGanttComponent, isStandalone: true, selector: "fly-gantt", inputs: { rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, dependencies: { classPropertyName: "dependencies", publicName: "dependencies", isSignal: true, isRequired: false, transformFunction: null }, zoom: { classPropertyName: "zoom", publicName: "zoom", isSignal: true, isRequired: false, transformFunction: null }, showToday: { classPropertyName: "showToday", publicName: "showToday", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, labelWidth: { classPropertyName: "labelWidth", publicName: "labelWidth", isSignal: true, isRequired: false, transformFunction: null }, resizableLabels: { classPropertyName: "resizableLabels", publicName: "resizableLabels", isSignal: true, isRequired: false, transformFunction: null }, rowHeight: { classPropertyName: "rowHeight", publicName: "rowHeight", isSignal: true, isRequired: false, transformFunction: null }, maxRows: { classPropertyName: "maxRows", publicName: "maxRows", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rowDatesChange: "rowDatesChange", dependencyCreate: "dependencyCreate", dependencyDelete: "dependencyDelete", rowClick: "rowClick", rowDblClick: "rowDblClick", labelWidthChange: "labelWidthChange" }, host: { properties: { "class.fly-gantt--rtl": "rtl()", "class.fly-gantt--readonly": "readonly()", "class.fly-gantt--resizing": "resizingLabels()", "attr.dir": "i18n.direction()" }, classAttribute: "fly-gantt" }, viewQueries: [{ propertyName: "bodyRef", first: true, predicate: ["bodySvg"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- Cross-scrolling canvas: a CSS grid whose header row and label column are sticky, so the\n time grid scrolls under a pinned header + label tree. All SVG geometry is pre-mirrored for\n RTL in the component (see gantt-scale mapX), so this template never branches on direction. -->\n<div\n class=\"fly-gantt__scroll\"\n role=\"grid\"\n tabindex=\"0\"\n [attr.aria-label]=\"'gantt.aria.grid' | translate\"\n [attr.aria-rowcount]=\"visibleRows().length\"\n (keydown)=\"onGridKeydown($event)\"\n>\n <div\n class=\"fly-gantt__canvas\"\n [style.grid-template-columns]=\"effectiveLabelWidth() + 'px ' + innerWidth() + 'px'\"\n [style.grid-template-rows]=\"HEADER_H + 'px ' + bodyHeight() + 'px'\"\n >\n <!-- \u2500\u2500 Corner (pinned both axes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"fly-gantt__corner\" [style.height.px]=\"HEADER_H\">\n @if (resizableLabels()) {\n <!-- The divider lives in the corner because the corner is the one cell pinned on BOTH\n axes: the grip stays reachable however far the user has scrolled. The full-height\n strip down the label pane below is the same gesture, presentational only. -->\n <div\n class=\"fly-gantt__splitter\"\n role=\"separator\"\n tabindex=\"0\"\n aria-orientation=\"vertical\"\n [attr.aria-label]=\"'gantt.aria.label_resize' | translate\"\n [attr.aria-valuenow]=\"effectiveLabelWidth()\"\n [attr.aria-valuemin]=\"MIN_LABEL_W\"\n [attr.aria-valuemax]=\"MAX_LABEL_W\"\n (pointerdown)=\"onLabelResizePointerDown($event)\"\n (keydown)=\"onLabelResizeKeydown($event)\"\n (dblclick)=\"resetLabelWidth()\"\n ></div>\n }\n </div>\n\n <!-- \u2500\u2500 Time header (pinned top) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <svg\n class=\"fly-gantt__time-header\"\n [attr.width]=\"innerWidth()\"\n [attr.height]=\"HEADER_H\"\n [attr.viewBox]=\"'0 0 ' + innerWidth() + ' ' + HEADER_H\"\n aria-hidden=\"true\"\n >\n @for (b of weekendBands(); track b.key) {\n <rect class=\"fly-gantt__weekend\" [attr.x]=\"b.x\" [attr.y]=\"HEADER_UPPER_H\" [attr.width]=\"b.width\" [attr.height]=\"HEADER_LOWER_H\" />\n }\n @for (t of upperTicks(); track t.key) {\n <line class=\"fly-gantt__tick-line\" [attr.x1]=\"t.x\" [attr.y1]=\"0\" [attr.x2]=\"t.x\" [attr.y2]=\"HEADER_H\" />\n <text class=\"fly-gantt__tick-label fly-gantt__tick-label--upper\" [attr.x]=\"t.x + t.width / 2\" [attr.y]=\"HEADER_UPPER_H / 2 + 4\">{{ t.label }}</text>\n }\n @for (t of lowerTicks(); track t.key) {\n <line class=\"fly-gantt__tick-line\" [attr.x1]=\"t.x\" [attr.y1]=\"HEADER_UPPER_H\" [attr.x2]=\"t.x\" [attr.y2]=\"HEADER_H\" />\n <text class=\"fly-gantt__tick-label\" [attr.x]=\"t.x + t.width / 2\" [attr.y]=\"HEADER_UPPER_H + HEADER_LOWER_H / 2 + 4\">{{ t.label }}</text>\n }\n @if (todayX() !== null) {\n <line class=\"fly-gantt__today\" [attr.x1]=\"todayX()\" [attr.y1]=\"HEADER_UPPER_H\" [attr.x2]=\"todayX()\" [attr.y2]=\"HEADER_H\" />\n }\n </svg>\n\n <!-- \u2500\u2500 Label tree (pinned inline-start) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"fly-gantt__labels\" role=\"rowgroup\">\n @for (vm of rowVms(); track vm.flat.row.id) {\n <div\n class=\"fly-gantt__label-row\"\n role=\"row\"\n tabindex=\"-1\"\n [class.fly-gantt__label-row--selected]=\"selectedId() === vm.flat.row.id\"\n [class.fly-gantt__label-row--tinted]=\"vm.tint !== null\"\n [style.height.px]=\"rowHeight()\"\n [style.--fly-gantt-row-tint]=\"vm.tint\"\n [attr.aria-selected]=\"selectedId() === vm.flat.row.id\"\n [attr.aria-label]=\"vm.ariaLabel\"\n (click)=\"onRowClick(vm.flat.row.id)\"\n (dblclick)=\"onRowDblClick(vm.flat.row.id)\"\n (keydown.enter)=\"onLabelRowKeydown($event, vm.flat.row.id)\"\n >\n <span class=\"fly-gantt__label-inner\" [style.padding-inline-start.px]=\"indentFor(vm.flat.depth)\">\n @if (vm.flat.hasChildren) {\n <button\n type=\"button\"\n class=\"fly-gantt__chevron\"\n [class.fly-gantt__chevron--collapsed]=\"isCollapsed(vm.flat.row.id)\"\n [attr.aria-label]=\"(isCollapsed(vm.flat.row.id) ? 'gantt.expand' : 'gantt.collapse') | translate\"\n [attr.aria-expanded]=\"!isCollapsed(vm.flat.row.id)\"\n (click)=\"toggleCollapse(vm.flat.row.id, $event)\"\n >\u25B8</button>\n } @else {\n <span class=\"fly-gantt__chevron-spacer\"></span>\n }\n @if (vm.color) {\n <!-- Colour chip: the row's own colour at full strength, so a milestone stays\n identifiable in the label pane where the band tint is deliberately faint. -->\n <span class=\"fly-gantt__label-swatch\" [style.background]=\"vm.color\" aria-hidden=\"true\"></span>\n }\n <span\n class=\"fly-gantt__label-text\"\n [class.fly-gantt__label-text--group]=\"vm.shape === 'group'\"\n [title]=\"vm.flat.row.label\"\n >{{ vm.flat.row.label }}</span>\n </span>\n </div>\n }\n @if (resizableLabels()) {\n <!-- aria-hidden so this presentational twin of the corner separator is not a second\n (and structurally invalid) child of the rowgroup in the accessibility tree. -->\n <div\n class=\"fly-gantt__splitter fly-gantt__splitter--rail\"\n aria-hidden=\"true\"\n (pointerdown)=\"onLabelResizePointerDown($event)\"\n (dblclick)=\"resetLabelWidth()\"\n ></div>\n }\n </div>\n\n <!-- \u2500\u2500 Time grid body \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <svg\n #bodySvg\n class=\"fly-gantt__body\"\n [attr.width]=\"innerWidth()\"\n [attr.height]=\"bodyHeight()\"\n [attr.viewBox]=\"'0 0 ' + innerWidth() + ' ' + bodyHeight()\"\n >\n <defs>\n <marker\n [attr.id]=\"markerId\"\n markerWidth=\"8\"\n markerHeight=\"8\"\n refX=\"6\"\n refY=\"4\"\n orient=\"auto\"\n markerUnits=\"userSpaceOnUse\"\n >\n <path class=\"fly-gantt__arrowhead\" d=\"M0,0 L7,4 L0,8 Z\" />\n </marker>\n </defs>\n\n <!-- Row colour bands sit at the very bottom of the stack, so the weekend shading and the\n gridlines below still read through them. -->\n @for (vm of tintedRows(); track vm.flat.row.id) {\n <rect\n class=\"fly-gantt__row-band\"\n [attr.x]=\"0\"\n [attr.y]=\"vm.y\"\n [attr.width]=\"innerWidth()\"\n [attr.height]=\"rowHeight()\"\n [style.fill]=\"vm.tint\"\n />\n }\n\n <!-- Weekend shading + vertical gridlines (behind the bars). -->\n @for (b of weekendBands(); track b.key) {\n <rect class=\"fly-gantt__weekend\" [attr.x]=\"b.x\" [attr.y]=\"0\" [attr.width]=\"b.width\" [attr.height]=\"bodyHeight()\" />\n }\n @for (t of lowerTicks(); track t.key) {\n <line class=\"fly-gantt__grid-line\" [attr.x1]=\"t.x\" [attr.y1]=\"0\" [attr.x2]=\"t.x\" [attr.y2]=\"bodyHeight()\" />\n }\n\n <!-- Rows: a full-width hit rect (click + link drop target) then the shape. -->\n @for (vm of rowVms(); track vm.flat.row.id) {\n <g class=\"fly-gantt__row\" [attr.data-row-id]=\"vm.flat.row.id\">\n <rect\n class=\"fly-gantt__row-hit\"\n [class.fly-gantt__row-hit--selected]=\"selectedId() === vm.flat.row.id\"\n [attr.x]=\"0\"\n [attr.y]=\"vm.y\"\n [attr.width]=\"innerWidth()\"\n [attr.height]=\"rowHeight()\"\n (pointerdown)=\"onRowClick(vm.flat.row.id)\"\n (dblclick)=\"onRowDblClick(vm.flat.row.id)\"\n />\n\n @switch (vm.shape) {\n @case ('group') {\n <path class=\"fly-gantt__group\" [attr.d]=\"groupPath(vm)\" [style.fill]=\"vm.color\" [style.stroke]=\"vm.color\" />\n }\n @case ('milestone') {\n <polygon\n class=\"fly-gantt__milestone\"\n [class.fly-gantt__milestone--editable]=\"vm.editable\"\n [attr.points]=\"diamondPoints(vm)\"\n [style.fill]=\"vm.color\"\n (pointerdown)=\"onBarPointerDown($event, vm, 'move')\"\n />\n @if (!readonly() && !vm.flat.row.readonly) {\n <!-- A diamond has no width, so one connector serves it; it reads as the finish\n end, which is what makes a milestone\u2192task drag the FS everyone expects. -->\n <circle\n class=\"fly-gantt__connector\"\n [attr.cx]=\"milestoneConnectorX(vm)\"\n [attr.cy]=\"vm.midY\"\n r=\"4\"\n [attr.aria-label]=\"'gantt.aria.link_handle' | translate: { label: vm.flat.row.label }\"\n (pointerdown)=\"onLinkPointerDown($event, vm, 'finish')\"\n />\n }\n }\n @case ('bar') {\n <g class=\"fly-gantt__bar-group\">\n <rect\n class=\"fly-gantt__bar\"\n [class.fly-gantt__bar--editable]=\"vm.editable\"\n [attr.x]=\"vm.x\"\n [attr.y]=\"barTop(vm)\"\n [attr.width]=\"vm.width\"\n [attr.height]=\"barHeight()\"\n rx=\"4\"\n [style.fill]=\"vm.color\"\n (pointerdown)=\"onBarPointerDown($event, vm, 'move')\"\n />\n @if (vm.progressWidth > 0) {\n <rect\n class=\"fly-gantt__progress\"\n [attr.x]=\"vm.x\"\n [attr.y]=\"barTop(vm)\"\n [attr.width]=\"vm.progressWidth\"\n [attr.height]=\"barHeight()\"\n rx=\"4\"\n />\n }\n @if (vm.editable) {\n <!-- Resize handles at both edges. -->\n <rect\n class=\"fly-gantt__handle\"\n [attr.x]=\"vm.x\"\n [attr.y]=\"barTop(vm)\"\n [attr.width]=\"HANDLE_W\"\n [attr.height]=\"barHeight()\"\n [attr.aria-label]=\"'gantt.aria.resize_start' | translate\"\n (pointerdown)=\"onBarPointerDown($event, vm, 'resize-start')\"\n />\n <rect\n class=\"fly-gantt__handle\"\n [attr.x]=\"vm.x + vm.width - HANDLE_W\"\n [attr.y]=\"barTop(vm)\"\n [attr.width]=\"HANDLE_W\"\n [attr.height]=\"barHeight()\"\n [attr.aria-label]=\"'gantt.aria.resize_end' | translate\"\n (pointerdown)=\"onBarPointerDown($event, vm, 'resize-end')\"\n />\n }\n @if (!readonly() && !vm.flat.row.readonly) {\n <!-- One connector per END. Dragging from the start edge yields an SS/SF link\n and from the finish edge an FS/FF one \u2014 the drop edge picks which. -->\n <circle\n class=\"fly-gantt__connector\"\n [attr.cx]=\"vm.startX\"\n [attr.cy]=\"vm.midY\"\n r=\"4\"\n [attr.aria-label]=\"'gantt.aria.link_handle_start' | translate: { label: vm.flat.row.label }\"\n (pointerdown)=\"onLinkPointerDown($event, vm, 'start')\"\n />\n <circle\n class=\"fly-gantt__connector\"\n [attr.cx]=\"vm.endX\"\n [attr.cy]=\"vm.midY\"\n r=\"4\"\n [attr.aria-label]=\"'gantt.aria.link_handle' | translate: { label: vm.flat.row.label }\"\n (pointerdown)=\"onLinkPointerDown($event, vm, 'finish')\"\n />\n }\n </g>\n }\n }\n </g>\n }\n\n <!-- Dependency arrows (skipped for missing / collapsed endpoints). Each carries a fat\n transparent twin so a 1.5px line is still clickable at pointer precision. -->\n @for (link of linkVms(); track link.key) {\n <g class=\"fly-gantt__link-group\" [class.fly-gantt__link-group--selected]=\"selectedLinkKey() === link.key\">\n <path\n class=\"fly-gantt__link-hit\"\n [attr.d]=\"link.path\"\n [attr.aria-label]=\"link.ariaLabel\"\n (click)=\"onLinkClick($event, link)\"\n />\n <path class=\"fly-gantt__link\" [attr.d]=\"link.path\" [attr.marker-end]=\"'url(#' + markerId + ')'\" />\n @if (selectedLinkKey() === link.key && !readonly()) {\n <g\n class=\"fly-gantt__link-delete\"\n [attr.transform]=\"'translate(' + link.badgeX + ',' + link.badgeY + ')'\"\n [attr.aria-label]=\"'gantt.aria.delete_link' | translate\"\n (click)=\"deleteLink($event, link)\"\n >\n <circle class=\"fly-gantt__link-delete-bg\" r=\"8\" />\n <path class=\"fly-gantt__link-delete-x\" d=\"M-3.5,-3.5 L3.5,3.5 M3.5,-3.5 L-3.5,3.5\" />\n </g>\n }\n </g>\n }\n\n <!-- In-flight link rubber band. -->\n @if (linkGesturePath(); as gp) {\n <path class=\"fly-gantt__link fly-gantt__link--ghost\" [attr.d]=\"gp\" [attr.marker-end]=\"'url(#' + markerId + ')'\" />\n }\n\n <!-- Today line (over the bars). -->\n @if (todayX() !== null) {\n <line class=\"fly-gantt__today\" [attr.x1]=\"todayX()\" [attr.y1]=\"0\" [attr.x2]=\"todayX()\" [attr.y2]=\"bodyHeight()\" />\n }\n </svg>\n </div>\n\n @if (visibleRows().length === 0) {\n <p class=\"fly-gantt__empty\">{{ 'gantt.no_data' | translate }}</p>\n }\n @if (overflowCount() > 0) {\n <p class=\"fly-gantt__overflow\">\n {{ 'gantt.overflow' | translate: { shown: maxRows(), total: maxRows() + overflowCount() } }}\n </p>\n }\n</div>\n\n<!-- Drag candidate tooltip (HTML overlay, follows the cursor). -->\n@if (dragTooltip(); as tip) {\n <div class=\"fly-gantt__tooltip\" [style.left.px]=\"tip.x\" [style.top.px]=\"tip.y\">{{ tip.text }}</div>\n}\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--bg-2, var(--surface-card, #fff));--_surface-alt: var(--bg-3, var(--glass-bg-elevated, #f8fafc));--_surface-hover: var(--bg-hover, var(--surface-hover, #f1f5f9));--_border: var(--w08, var(--surface-border, #e2e8f0));--_border-strong: var(--w1, var(--surface-border, #cbd5e1));--_grid-line: var(--line-3, var(--surface-border, #eef2f7));--_text: var(--ink, var(--text-color, #0f172a));--_text-subtle: var(--ink-3, var(--text-color-secondary, #64748b));--_text-faint: var(--ink-4, var(--text-color-secondary, #94a3b8));--_accent: var(--accent);--_bar: var(--_accent);--_bar-progress: color-mix(in srgb, var(--_accent) 72%, #000);--_weekend: color-mix(in srgb, var(--_text-subtle) 9%, transparent);--_today: var(--danger, #ef4444);--_link: var(--_text-subtle);--_radius: var(--r-lg, 8px);--_transition: var(--t-state, .12s ease);display:block;inline-size:100%;block-size:100%;min-block-size:200px;color:var(--_text);font-size:13px}:host(.fly-gantt--resizing){cursor:col-resize;-webkit-user-select:none;user-select:none}.fly-gantt__scroll{position:relative;inline-size:100%;block-size:100%;overflow:auto;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);box-shadow:var(--shadow-card, none);outline:none}.fly-gantt__scroll:focus-visible{outline:2px solid var(--_accent);outline-offset:-2px}.fly-gantt__canvas{display:grid;position:relative}.fly-gantt__corner{position:sticky;inset-block-start:0;inset-inline-start:0;z-index:4;background:var(--_surface-alt);-webkit-backdrop-filter:blur(12px) saturate(140%);backdrop-filter:blur(12px) saturate(140%);border-inline-end:1px solid var(--_border);border-block-end:1px solid var(--_border-strong)}.fly-gantt__time-header{position:sticky;inset-block-start:0;z-index:3;display:block;background:var(--_surface-alt);-webkit-backdrop-filter:blur(12px) saturate(140%);backdrop-filter:blur(12px) saturate(140%);border-block-end:1px solid var(--_border-strong)}.fly-gantt__tick-line{stroke:var(--_grid-line);stroke-width:1}.fly-gantt__tick-label{fill:var(--_text-subtle);font-size:11px;text-anchor:middle;dominant-baseline:middle}.fly-gantt__tick-label--upper{fill:var(--_text);font-weight:600}.fly-gantt__splitter{position:absolute;inset-block:0;inset-inline-end:-3px;inline-size:7px;z-index:5;cursor:col-resize;touch-action:none}.fly-gantt__splitter:after{content:\"\";position:absolute;inset-block:0;inset-inline-start:3px;inline-size:1px;background:transparent;transition:background var(--_transition)}.fly-gantt__splitter:hover:after,.fly-gantt__splitter:focus-visible:after{background:var(--_accent)}.fly-gantt__splitter:focus-visible{outline:2px solid var(--_accent);outline-offset:-1px}:host(.fly-gantt--resizing) .fly-gantt__splitter:after{background:var(--_accent)}.fly-gantt__labels{position:sticky;inset-inline-start:0;z-index:2;background:var(--_surface);border-inline-end:1px solid var(--_border)}.fly-gantt__label-row{position:relative;display:flex;align-items:center;border-block-end:1px solid var(--_grid-line);cursor:pointer;transition:background var(--_transition)}.fly-gantt__label-row:hover{background:var(--_surface-hover)}.fly-gantt__label-row--selected{background:var(--_surface-hover);box-shadow:inset 3px 0 0 0 var(--_accent)}.fly-gantt__label-row--tinted:before{content:\"\";position:absolute;inset:0;background:var(--fly-gantt-row-tint);pointer-events:none}.fly-gantt__label-inner{position:relative;display:flex;align-items:center;gap:4px;inline-size:100%;min-inline-size:0;padding-inline-end:8px}.fly-gantt__label-swatch{flex:0 0 auto;inline-size:8px;block-size:8px;border-radius:2px}.fly-gantt__chevron{flex:0 0 auto;inline-size:18px;block-size:18px;padding:0;border:none;background:transparent;color:var(--_text-subtle);cursor:pointer;line-height:1;transform:rotate(90deg);transition:transform var(--_transition)}.fly-gantt__chevron--collapsed{transform:rotate(0)}:host(.fly-gantt--rtl) .fly-gantt__chevron--collapsed{transform:rotate(180deg)}.fly-gantt__chevron:focus-visible{outline:2px solid var(--_accent);outline-offset:1px;border-radius:4px}.fly-gantt__chevron-spacer{flex:0 0 auto;inline-size:18px}.fly-gantt__label-text{min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-gantt__label-text--group{font-weight:600}.fly-gantt__body{display:block;touch-action:pan-x pan-y}.fly-gantt__row-band{pointer-events:none}.fly-gantt__weekend{fill:var(--_weekend)}.fly-gantt__grid-line{stroke:var(--_grid-line);stroke-width:1}.fly-gantt__row-hit{fill:transparent}.fly-gantt__row-hit--selected{fill:var(--_surface-hover)}.fly-gantt__bar{fill:var(--_bar);stroke:none}.fly-gantt__bar--editable{cursor:grab}.fly-gantt__progress{fill:var(--_bar-progress);pointer-events:none}.fly-gantt__handle{fill:transparent;cursor:ew-resize}.fly-gantt__connector{fill:var(--_surface);stroke:var(--_accent);stroke-width:1.5;cursor:crosshair;opacity:0;transition:opacity var(--_transition)}.fly-gantt__row:hover .fly-gantt__connector{opacity:1}.fly-gantt__milestone{fill:var(--_accent);stroke:var(--_surface);stroke-width:1}.fly-gantt__milestone--editable{cursor:grab}.fly-gantt__group{fill:var(--_text-subtle);stroke:var(--_text-subtle)}.fly-gantt__link{fill:none;stroke:var(--_link);stroke-width:1.5;pointer-events:none}.fly-gantt__link-hit{fill:none;stroke:transparent;stroke-width:11;pointer-events:stroke;cursor:pointer}.fly-gantt__link-group:hover .fly-gantt__link{stroke:var(--_text)}.fly-gantt__link-group--selected .fly-gantt__link{stroke:var(--_accent);stroke-width:2.5}.fly-gantt__link-delete-bg{fill:var(--_accent)}.fly-gantt__link-delete{cursor:pointer}.fly-gantt__link-delete-x{stroke:var(--_surface);stroke-width:1.75;stroke-linecap:round;fill:none;pointer-events:none}.fly-gantt__link--ghost{stroke-dasharray:4 3;opacity:.8}.fly-gantt__arrowhead{fill:var(--_link)}.fly-gantt__today{stroke:var(--_today);stroke-width:1.5;stroke-dasharray:3 3;pointer-events:none}.fly-gantt__empty,.fly-gantt__overflow{margin:0;padding:12px;color:var(--_text-faint);font-size:12px;text-align:center}.fly-gantt__tooltip{position:fixed;z-index:20;transform:translate(12px,16px);padding:4px 8px;border-radius:6px;background:var(--_text);color:var(--_surface);font-size:12px;white-space:nowrap;pointer-events:none;direction:ltr;unicode-bidi:isolate}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
13833
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyGanttComponent, isStandalone: true, selector: "fly-gantt", inputs: { rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, dependencies: { classPropertyName: "dependencies", publicName: "dependencies", isSignal: true, isRequired: false, transformFunction: null }, zoom: { classPropertyName: "zoom", publicName: "zoom", isSignal: true, isRequired: false, transformFunction: null }, showToday: { classPropertyName: "showToday", publicName: "showToday", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, labelWidth: { classPropertyName: "labelWidth", publicName: "labelWidth", isSignal: true, isRequired: false, transformFunction: null }, resizableLabels: { classPropertyName: "resizableLabels", publicName: "resizableLabels", isSignal: true, isRequired: false, transformFunction: null }, rowHeight: { classPropertyName: "rowHeight", publicName: "rowHeight", isSignal: true, isRequired: false, transformFunction: null }, maxRows: { classPropertyName: "maxRows", publicName: "maxRows", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rowDatesChange: "rowDatesChange", dependencyCreate: "dependencyCreate", dependencyDelete: "dependencyDelete", rowClick: "rowClick", rowDblClick: "rowDblClick", labelWidthChange: "labelWidthChange" }, host: { properties: { "class.fly-gantt--rtl": "rtl()", "class.fly-gantt--readonly": "readonly()", "class.fly-gantt--resizing": "resizingLabels()", "attr.dir": "i18n.direction()" }, classAttribute: "fly-gantt" }, viewQueries: [{ propertyName: "bodyRef", first: true, predicate: ["bodySvg"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- Cross-scrolling canvas: a CSS grid whose header row and label column are sticky, so the\r\n time grid scrolls under a pinned header + label tree. All SVG geometry is pre-mirrored for\r\n RTL in the component (see gantt-scale mapX), so this template never branches on direction. -->\r\n<div\r\n class=\"fly-gantt__scroll\"\r\n role=\"grid\"\r\n tabindex=\"0\"\r\n [attr.aria-label]=\"'gantt.aria.grid' | translate\"\r\n [attr.aria-rowcount]=\"visibleRows().length\"\r\n (keydown)=\"onGridKeydown($event)\"\r\n>\r\n <div\r\n class=\"fly-gantt__canvas\"\r\n [style.grid-template-columns]=\"effectiveLabelWidth() + 'px ' + innerWidth() + 'px'\"\r\n [style.grid-template-rows]=\"HEADER_H + 'px ' + bodyHeight() + 'px'\"\r\n >\r\n <!-- \u2500\u2500 Corner (pinned both axes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <div class=\"fly-gantt__corner\" [style.height.px]=\"HEADER_H\">\r\n @if (resizableLabels()) {\r\n <!-- The divider lives in the corner because the corner is the one cell pinned on BOTH\r\n axes: the grip stays reachable however far the user has scrolled. The full-height\r\n strip down the label pane below is the same gesture, presentational only. -->\r\n <div\r\n class=\"fly-gantt__splitter\"\r\n role=\"separator\"\r\n tabindex=\"0\"\r\n aria-orientation=\"vertical\"\r\n [attr.aria-label]=\"'gantt.aria.label_resize' | translate\"\r\n [attr.aria-valuenow]=\"effectiveLabelWidth()\"\r\n [attr.aria-valuemin]=\"MIN_LABEL_W\"\r\n [attr.aria-valuemax]=\"MAX_LABEL_W\"\r\n (pointerdown)=\"onLabelResizePointerDown($event)\"\r\n (keydown)=\"onLabelResizeKeydown($event)\"\r\n (dblclick)=\"resetLabelWidth()\"\r\n ></div>\r\n }\r\n </div>\r\n\r\n <!-- \u2500\u2500 Time header (pinned top) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <svg\r\n class=\"fly-gantt__time-header\"\r\n [attr.width]=\"innerWidth()\"\r\n [attr.height]=\"HEADER_H\"\r\n [attr.viewBox]=\"'0 0 ' + innerWidth() + ' ' + HEADER_H\"\r\n aria-hidden=\"true\"\r\n >\r\n @for (b of weekendBands(); track b.key) {\r\n <rect class=\"fly-gantt__weekend\" [attr.x]=\"b.x\" [attr.y]=\"HEADER_UPPER_H\" [attr.width]=\"b.width\" [attr.height]=\"HEADER_LOWER_H\" />\r\n }\r\n @for (t of upperTicks(); track t.key) {\r\n <line class=\"fly-gantt__tick-line\" [attr.x1]=\"t.x\" [attr.y1]=\"0\" [attr.x2]=\"t.x\" [attr.y2]=\"HEADER_H\" />\r\n <text class=\"fly-gantt__tick-label fly-gantt__tick-label--upper\" [attr.x]=\"t.x + t.width / 2\" [attr.y]=\"HEADER_UPPER_H / 2 + 4\">{{ t.label }}</text>\r\n }\r\n @for (t of lowerTicks(); track t.key) {\r\n <line class=\"fly-gantt__tick-line\" [attr.x1]=\"t.x\" [attr.y1]=\"HEADER_UPPER_H\" [attr.x2]=\"t.x\" [attr.y2]=\"HEADER_H\" />\r\n <text class=\"fly-gantt__tick-label\" [attr.x]=\"t.x + t.width / 2\" [attr.y]=\"HEADER_UPPER_H + HEADER_LOWER_H / 2 + 4\">{{ t.label }}</text>\r\n }\r\n @if (todayX() !== null) {\r\n <line class=\"fly-gantt__today\" [attr.x1]=\"todayX()\" [attr.y1]=\"HEADER_UPPER_H\" [attr.x2]=\"todayX()\" [attr.y2]=\"HEADER_H\" />\r\n }\r\n </svg>\r\n\r\n <!-- \u2500\u2500 Label tree (pinned inline-start) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <div class=\"fly-gantt__labels\" role=\"rowgroup\">\r\n @for (vm of rowVms(); track vm.flat.row.id) {\r\n <div\r\n class=\"fly-gantt__label-row\"\r\n role=\"row\"\r\n tabindex=\"-1\"\r\n [class.fly-gantt__label-row--selected]=\"selectedId() === vm.flat.row.id\"\r\n [class.fly-gantt__label-row--tinted]=\"vm.tint !== null\"\r\n [style.height.px]=\"rowHeight()\"\r\n [style.--fly-gantt-row-tint]=\"vm.tint\"\r\n [attr.aria-selected]=\"selectedId() === vm.flat.row.id\"\r\n [attr.aria-label]=\"vm.ariaLabel\"\r\n (click)=\"onRowClick(vm.flat.row.id)\"\r\n (dblclick)=\"onRowDblClick(vm.flat.row.id)\"\r\n (keydown.enter)=\"onLabelRowKeydown($event, vm.flat.row.id)\"\r\n >\r\n <span class=\"fly-gantt__label-inner\" [style.padding-inline-start.px]=\"indentFor(vm.flat.depth)\">\r\n @if (vm.flat.hasChildren) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-gantt__chevron\"\r\n [class.fly-gantt__chevron--collapsed]=\"isCollapsed(vm.flat.row.id)\"\r\n [attr.aria-label]=\"(isCollapsed(vm.flat.row.id) ? 'gantt.expand' : 'gantt.collapse') | translate\"\r\n [attr.aria-expanded]=\"!isCollapsed(vm.flat.row.id)\"\r\n (click)=\"toggleCollapse(vm.flat.row.id, $event)\"\r\n >\u25B8</button>\r\n } @else {\r\n <span class=\"fly-gantt__chevron-spacer\"></span>\r\n }\r\n @if (vm.color) {\r\n <!-- Colour chip: the row's own colour at full strength, so a milestone stays\r\n identifiable in the label pane where the band tint is deliberately faint. -->\r\n <span class=\"fly-gantt__label-swatch\" [style.background]=\"vm.color\" aria-hidden=\"true\"></span>\r\n }\r\n <span\r\n class=\"fly-gantt__label-text\"\r\n [class.fly-gantt__label-text--group]=\"vm.shape === 'group'\"\r\n [title]=\"vm.flat.row.label\"\r\n >{{ vm.flat.row.label }}</span>\r\n </span>\r\n </div>\r\n }\r\n @if (resizableLabels()) {\r\n <!-- aria-hidden so this presentational twin of the corner separator is not a second\r\n (and structurally invalid) child of the rowgroup in the accessibility tree. -->\r\n <div\r\n class=\"fly-gantt__splitter fly-gantt__splitter--rail\"\r\n aria-hidden=\"true\"\r\n (pointerdown)=\"onLabelResizePointerDown($event)\"\r\n (dblclick)=\"resetLabelWidth()\"\r\n ></div>\r\n }\r\n </div>\r\n\r\n <!-- \u2500\u2500 Time grid body \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <svg\r\n #bodySvg\r\n class=\"fly-gantt__body\"\r\n [attr.width]=\"innerWidth()\"\r\n [attr.height]=\"bodyHeight()\"\r\n [attr.viewBox]=\"'0 0 ' + innerWidth() + ' ' + bodyHeight()\"\r\n >\r\n <defs>\r\n <marker\r\n [attr.id]=\"markerId\"\r\n markerWidth=\"8\"\r\n markerHeight=\"8\"\r\n refX=\"6\"\r\n refY=\"4\"\r\n orient=\"auto\"\r\n markerUnits=\"userSpaceOnUse\"\r\n >\r\n <path class=\"fly-gantt__arrowhead\" d=\"M0,0 L7,4 L0,8 Z\" />\r\n </marker>\r\n </defs>\r\n\r\n <!-- Row colour bands sit at the very bottom of the stack, so the weekend shading and the\r\n gridlines below still read through them. -->\r\n @for (vm of tintedRows(); track vm.flat.row.id) {\r\n <rect\r\n class=\"fly-gantt__row-band\"\r\n [attr.x]=\"0\"\r\n [attr.y]=\"vm.y\"\r\n [attr.width]=\"innerWidth()\"\r\n [attr.height]=\"rowHeight()\"\r\n [style.fill]=\"vm.tint\"\r\n />\r\n }\r\n\r\n <!-- Weekend shading + vertical gridlines (behind the bars). -->\r\n @for (b of weekendBands(); track b.key) {\r\n <rect class=\"fly-gantt__weekend\" [attr.x]=\"b.x\" [attr.y]=\"0\" [attr.width]=\"b.width\" [attr.height]=\"bodyHeight()\" />\r\n }\r\n @for (t of lowerTicks(); track t.key) {\r\n <line class=\"fly-gantt__grid-line\" [attr.x1]=\"t.x\" [attr.y1]=\"0\" [attr.x2]=\"t.x\" [attr.y2]=\"bodyHeight()\" />\r\n }\r\n\r\n <!-- Rows: a full-width hit rect (click + link drop target) then the shape. -->\r\n @for (vm of rowVms(); track vm.flat.row.id) {\r\n <g class=\"fly-gantt__row\" [attr.data-row-id]=\"vm.flat.row.id\">\r\n <rect\r\n class=\"fly-gantt__row-hit\"\r\n [class.fly-gantt__row-hit--selected]=\"selectedId() === vm.flat.row.id\"\r\n [attr.x]=\"0\"\r\n [attr.y]=\"vm.y\"\r\n [attr.width]=\"innerWidth()\"\r\n [attr.height]=\"rowHeight()\"\r\n (pointerdown)=\"onRowClick(vm.flat.row.id)\"\r\n (dblclick)=\"onRowDblClick(vm.flat.row.id)\"\r\n />\r\n\r\n @switch (vm.shape) {\r\n @case ('group') {\r\n <path class=\"fly-gantt__group\" [attr.d]=\"groupPath(vm)\" [style.fill]=\"vm.color\" [style.stroke]=\"vm.color\" />\r\n }\r\n @case ('milestone') {\r\n <polygon\r\n class=\"fly-gantt__milestone\"\r\n [class.fly-gantt__milestone--editable]=\"vm.editable\"\r\n [attr.points]=\"diamondPoints(vm)\"\r\n [style.fill]=\"vm.color\"\r\n (pointerdown)=\"onBarPointerDown($event, vm, 'move')\"\r\n />\r\n @if (!readonly() && !vm.flat.row.readonly) {\r\n <!-- A diamond has no width, so one connector serves it; it reads as the finish\r\n end, which is what makes a milestone\u2192task drag the FS everyone expects. -->\r\n <circle\r\n class=\"fly-gantt__connector\"\r\n [attr.cx]=\"milestoneConnectorX(vm)\"\r\n [attr.cy]=\"vm.midY\"\r\n r=\"4\"\r\n [attr.aria-label]=\"'gantt.aria.link_handle' | translate: { label: vm.flat.row.label }\"\r\n (pointerdown)=\"onLinkPointerDown($event, vm, 'finish')\"\r\n />\r\n }\r\n }\r\n @case ('bar') {\r\n <g class=\"fly-gantt__bar-group\">\r\n <rect\r\n class=\"fly-gantt__bar\"\r\n [class.fly-gantt__bar--editable]=\"vm.editable\"\r\n [attr.x]=\"vm.x\"\r\n [attr.y]=\"barTop(vm)\"\r\n [attr.width]=\"vm.width\"\r\n [attr.height]=\"barHeight()\"\r\n rx=\"4\"\r\n [style.fill]=\"vm.color\"\r\n (pointerdown)=\"onBarPointerDown($event, vm, 'move')\"\r\n />\r\n @if (vm.progressWidth > 0) {\r\n <rect\r\n class=\"fly-gantt__progress\"\r\n [attr.x]=\"vm.x\"\r\n [attr.y]=\"barTop(vm)\"\r\n [attr.width]=\"vm.progressWidth\"\r\n [attr.height]=\"barHeight()\"\r\n rx=\"4\"\r\n />\r\n }\r\n @if (vm.editable) {\r\n <!-- Resize handles at both edges. -->\r\n <rect\r\n class=\"fly-gantt__handle\"\r\n [attr.x]=\"vm.x\"\r\n [attr.y]=\"barTop(vm)\"\r\n [attr.width]=\"HANDLE_W\"\r\n [attr.height]=\"barHeight()\"\r\n [attr.aria-label]=\"'gantt.aria.resize_start' | translate\"\r\n (pointerdown)=\"onBarPointerDown($event, vm, 'resize-start')\"\r\n />\r\n <rect\r\n class=\"fly-gantt__handle\"\r\n [attr.x]=\"vm.x + vm.width - HANDLE_W\"\r\n [attr.y]=\"barTop(vm)\"\r\n [attr.width]=\"HANDLE_W\"\r\n [attr.height]=\"barHeight()\"\r\n [attr.aria-label]=\"'gantt.aria.resize_end' | translate\"\r\n (pointerdown)=\"onBarPointerDown($event, vm, 'resize-end')\"\r\n />\r\n }\r\n @if (!readonly() && !vm.flat.row.readonly) {\r\n <!-- One connector per END. Dragging from the start edge yields an SS/SF link\r\n and from the finish edge an FS/FF one \u2014 the drop edge picks which. -->\r\n <circle\r\n class=\"fly-gantt__connector\"\r\n [attr.cx]=\"vm.startX\"\r\n [attr.cy]=\"vm.midY\"\r\n r=\"4\"\r\n [attr.aria-label]=\"'gantt.aria.link_handle_start' | translate: { label: vm.flat.row.label }\"\r\n (pointerdown)=\"onLinkPointerDown($event, vm, 'start')\"\r\n />\r\n <circle\r\n class=\"fly-gantt__connector\"\r\n [attr.cx]=\"vm.endX\"\r\n [attr.cy]=\"vm.midY\"\r\n r=\"4\"\r\n [attr.aria-label]=\"'gantt.aria.link_handle' | translate: { label: vm.flat.row.label }\"\r\n (pointerdown)=\"onLinkPointerDown($event, vm, 'finish')\"\r\n />\r\n }\r\n </g>\r\n }\r\n }\r\n </g>\r\n }\r\n\r\n <!-- Dependency arrows (skipped for missing / collapsed endpoints). Each carries a fat\r\n transparent twin so a 1.5px line is still clickable at pointer precision. -->\r\n @for (link of linkVms(); track link.key) {\r\n <g class=\"fly-gantt__link-group\" [class.fly-gantt__link-group--selected]=\"selectedLinkKey() === link.key\">\r\n <path\r\n class=\"fly-gantt__link-hit\"\r\n [attr.d]=\"link.path\"\r\n [attr.aria-label]=\"link.ariaLabel\"\r\n (click)=\"onLinkClick($event, link)\"\r\n />\r\n <path class=\"fly-gantt__link\" [attr.d]=\"link.path\" [attr.marker-end]=\"'url(#' + markerId + ')'\" />\r\n @if (selectedLinkKey() === link.key && !readonly()) {\r\n <g\r\n class=\"fly-gantt__link-delete\"\r\n [attr.transform]=\"'translate(' + link.badgeX + ',' + link.badgeY + ')'\"\r\n [attr.aria-label]=\"'gantt.aria.delete_link' | translate\"\r\n (click)=\"deleteLink($event, link)\"\r\n >\r\n <circle class=\"fly-gantt__link-delete-bg\" r=\"8\" />\r\n <path class=\"fly-gantt__link-delete-x\" d=\"M-3.5,-3.5 L3.5,3.5 M3.5,-3.5 L-3.5,3.5\" />\r\n </g>\r\n }\r\n </g>\r\n }\r\n\r\n <!-- In-flight link rubber band. -->\r\n @if (linkGesturePath(); as gp) {\r\n <path class=\"fly-gantt__link fly-gantt__link--ghost\" [attr.d]=\"gp\" [attr.marker-end]=\"'url(#' + markerId + ')'\" />\r\n }\r\n\r\n <!-- Today line (over the bars). -->\r\n @if (todayX() !== null) {\r\n <line class=\"fly-gantt__today\" [attr.x1]=\"todayX()\" [attr.y1]=\"0\" [attr.x2]=\"todayX()\" [attr.y2]=\"bodyHeight()\" />\r\n }\r\n </svg>\r\n </div>\r\n\r\n @if (visibleRows().length === 0) {\r\n <p class=\"fly-gantt__empty\">{{ 'gantt.no_data' | translate }}</p>\r\n }\r\n @if (overflowCount() > 0) {\r\n <p class=\"fly-gantt__overflow\">\r\n {{ 'gantt.overflow' | translate: { shown: maxRows(), total: maxRows() + overflowCount() } }}\r\n </p>\r\n }\r\n</div>\r\n\r\n<!-- Drag candidate tooltip (HTML overlay, follows the cursor). -->\r\n@if (dragTooltip(); as tip) {\r\n <div class=\"fly-gantt__tooltip\" [style.left.px]=\"tip.x\" [style.top.px]=\"tip.y\">{{ tip.text }}</div>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--bg-2, var(--surface-card, #fff));--_surface-alt: var(--bg-3, var(--glass-bg-elevated, #f8fafc));--_surface-hover: var(--bg-hover, var(--surface-hover, #f1f5f9));--_border: var(--w08, var(--surface-border, #e2e8f0));--_border-strong: var(--w1, var(--surface-border, #cbd5e1));--_grid-line: var(--line-3, var(--surface-border, #eef2f7));--_text: var(--ink, var(--text-color, #0f172a));--_text-subtle: var(--ink-3, var(--text-color-secondary, #64748b));--_text-faint: var(--ink-4, var(--text-color-secondary, #94a3b8));--_accent: var(--accent);--_bar: var(--_accent);--_bar-progress: color-mix(in srgb, var(--_accent) 72%, #000);--_weekend: color-mix(in srgb, var(--_text-subtle) 9%, transparent);--_today: var(--danger, #ef4444);--_link: var(--_text-subtle);--_radius: var(--r-lg, 8px);--_transition: var(--t-state, .12s ease);display:block;inline-size:100%;block-size:100%;min-block-size:200px;color:var(--_text);font-size:13px}:host(.fly-gantt--resizing){cursor:col-resize;-webkit-user-select:none;user-select:none}.fly-gantt__scroll{position:relative;inline-size:100%;block-size:100%;overflow:auto;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);box-shadow:var(--shadow-card, none);outline:none}.fly-gantt__scroll:focus-visible{outline:2px solid var(--_accent);outline-offset:-2px}.fly-gantt__canvas{display:grid;position:relative}.fly-gantt__corner{position:sticky;inset-block-start:0;inset-inline-start:0;z-index:4;background:var(--_surface-alt);-webkit-backdrop-filter:blur(12px) saturate(140%);backdrop-filter:blur(12px) saturate(140%);border-inline-end:1px solid var(--_border);border-block-end:1px solid var(--_border-strong)}.fly-gantt__time-header{position:sticky;inset-block-start:0;z-index:3;display:block;background:var(--_surface-alt);-webkit-backdrop-filter:blur(12px) saturate(140%);backdrop-filter:blur(12px) saturate(140%);border-block-end:1px solid var(--_border-strong)}.fly-gantt__tick-line{stroke:var(--_grid-line);stroke-width:1}.fly-gantt__tick-label{fill:var(--_text-subtle);font-size:11px;text-anchor:middle;dominant-baseline:middle}.fly-gantt__tick-label--upper{fill:var(--_text);font-weight:600}.fly-gantt__splitter{position:absolute;inset-block:0;inset-inline-end:-3px;inline-size:7px;z-index:5;cursor:col-resize;touch-action:none}.fly-gantt__splitter:after{content:\"\";position:absolute;inset-block:0;inset-inline-start:3px;inline-size:1px;background:transparent;transition:background var(--_transition)}.fly-gantt__splitter:hover:after,.fly-gantt__splitter:focus-visible:after{background:var(--_accent)}.fly-gantt__splitter:focus-visible{outline:2px solid var(--_accent);outline-offset:-1px}:host(.fly-gantt--resizing) .fly-gantt__splitter:after{background:var(--_accent)}.fly-gantt__labels{position:sticky;inset-inline-start:0;z-index:2;background:var(--_surface);border-inline-end:1px solid var(--_border)}.fly-gantt__label-row{position:relative;display:flex;align-items:center;border-block-end:1px solid var(--_grid-line);cursor:pointer;transition:background var(--_transition)}.fly-gantt__label-row:hover{background:var(--_surface-hover)}.fly-gantt__label-row--selected{background:var(--_surface-hover);box-shadow:inset 3px 0 0 0 var(--_accent)}.fly-gantt__label-row--tinted:before{content:\"\";position:absolute;inset:0;background:var(--fly-gantt-row-tint);pointer-events:none}.fly-gantt__label-inner{position:relative;display:flex;align-items:center;gap:4px;inline-size:100%;min-inline-size:0;padding-inline-end:8px}.fly-gantt__label-swatch{flex:0 0 auto;inline-size:8px;block-size:8px;border-radius:2px}.fly-gantt__chevron{flex:0 0 auto;inline-size:18px;block-size:18px;padding:0;border:none;background:transparent;color:var(--_text-subtle);cursor:pointer;line-height:1;transform:rotate(90deg);transition:transform var(--_transition)}.fly-gantt__chevron--collapsed{transform:rotate(0)}:host(.fly-gantt--rtl) .fly-gantt__chevron--collapsed{transform:rotate(180deg)}.fly-gantt__chevron:focus-visible{outline:2px solid var(--_accent);outline-offset:1px;border-radius:4px}.fly-gantt__chevron-spacer{flex:0 0 auto;inline-size:18px}.fly-gantt__label-text{min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-gantt__label-text--group{font-weight:600}.fly-gantt__body{display:block;touch-action:pan-x pan-y}.fly-gantt__row-band{pointer-events:none}.fly-gantt__weekend{fill:var(--_weekend)}.fly-gantt__grid-line{stroke:var(--_grid-line);stroke-width:1}.fly-gantt__row-hit{fill:transparent}.fly-gantt__row-hit--selected{fill:var(--_surface-hover)}.fly-gantt__bar{fill:var(--_bar);stroke:none}.fly-gantt__bar--editable{cursor:grab}.fly-gantt__progress{fill:var(--_bar-progress);pointer-events:none}.fly-gantt__handle{fill:transparent;cursor:ew-resize}.fly-gantt__connector{fill:var(--_surface);stroke:var(--_accent);stroke-width:1.5;cursor:crosshair;opacity:0;transition:opacity var(--_transition)}.fly-gantt__row:hover .fly-gantt__connector{opacity:1}.fly-gantt__milestone{fill:var(--_accent);stroke:var(--_surface);stroke-width:1}.fly-gantt__milestone--editable{cursor:grab}.fly-gantt__group{fill:var(--_text-subtle);stroke:var(--_text-subtle)}.fly-gantt__link{fill:none;stroke:var(--_link);stroke-width:1.5;pointer-events:none}.fly-gantt__link-hit{fill:none;stroke:transparent;stroke-width:11;pointer-events:stroke;cursor:pointer}.fly-gantt__link-group:hover .fly-gantt__link{stroke:var(--_text)}.fly-gantt__link-group--selected .fly-gantt__link{stroke:var(--_accent);stroke-width:2.5}.fly-gantt__link-delete-bg{fill:var(--_accent)}.fly-gantt__link-delete{cursor:pointer}.fly-gantt__link-delete-x{stroke:var(--_surface);stroke-width:1.75;stroke-linecap:round;fill:none;pointer-events:none}.fly-gantt__link--ghost{stroke-dasharray:4 3;opacity:.8}.fly-gantt__arrowhead{fill:var(--_link)}.fly-gantt__today{stroke:var(--_today);stroke-width:1.5;stroke-dasharray:3 3;pointer-events:none}.fly-gantt__empty,.fly-gantt__overflow{margin:0;padding:12px;color:var(--_text-faint);font-size:12px;text-align:center}.fly-gantt__tooltip{position:fixed;z-index:20;transform:translate(12px,16px);padding:4px 8px;border-radius:6px;background:var(--_text);color:var(--_surface);font-size:12px;white-space:nowrap;pointer-events:none;direction:ltr;unicode-bidi:isolate}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
13546
13834
|
}
|
|
13547
13835
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyGanttComponent, decorators: [{
|
|
13548
13836
|
type: Component,
|
|
@@ -13552,7 +13840,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
|
|
|
13552
13840
|
'[class.fly-gantt--readonly]': 'readonly()',
|
|
13553
13841
|
'[class.fly-gantt--resizing]': 'resizingLabels()',
|
|
13554
13842
|
'[attr.dir]': 'i18n.direction()',
|
|
13555
|
-
}, template: "<!-- Cross-scrolling canvas: a CSS grid whose header row and label column are sticky, so the\n time grid scrolls under a pinned header + label tree. All SVG geometry is pre-mirrored for\n RTL in the component (see gantt-scale mapX), so this template never branches on direction. -->\n<div\n class=\"fly-gantt__scroll\"\n role=\"grid\"\n tabindex=\"0\"\n [attr.aria-label]=\"'gantt.aria.grid' | translate\"\n [attr.aria-rowcount]=\"visibleRows().length\"\n (keydown)=\"onGridKeydown($event)\"\n>\n <div\n class=\"fly-gantt__canvas\"\n [style.grid-template-columns]=\"effectiveLabelWidth() + 'px ' + innerWidth() + 'px'\"\n [style.grid-template-rows]=\"HEADER_H + 'px ' + bodyHeight() + 'px'\"\n >\n <!-- \u2500\u2500 Corner (pinned both axes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"fly-gantt__corner\" [style.height.px]=\"HEADER_H\">\n @if (resizableLabels()) {\n <!-- The divider lives in the corner because the corner is the one cell pinned on BOTH\n axes: the grip stays reachable however far the user has scrolled. The full-height\n strip down the label pane below is the same gesture, presentational only. -->\n <div\n class=\"fly-gantt__splitter\"\n role=\"separator\"\n tabindex=\"0\"\n aria-orientation=\"vertical\"\n [attr.aria-label]=\"'gantt.aria.label_resize' | translate\"\n [attr.aria-valuenow]=\"effectiveLabelWidth()\"\n [attr.aria-valuemin]=\"MIN_LABEL_W\"\n [attr.aria-valuemax]=\"MAX_LABEL_W\"\n (pointerdown)=\"onLabelResizePointerDown($event)\"\n (keydown)=\"onLabelResizeKeydown($event)\"\n (dblclick)=\"resetLabelWidth()\"\n ></div>\n }\n </div>\n\n <!-- \u2500\u2500 Time header (pinned top) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <svg\n class=\"fly-gantt__time-header\"\n [attr.width]=\"innerWidth()\"\n [attr.height]=\"HEADER_H\"\n [attr.viewBox]=\"'0 0 ' + innerWidth() + ' ' + HEADER_H\"\n aria-hidden=\"true\"\n >\n @for (b of weekendBands(); track b.key) {\n <rect class=\"fly-gantt__weekend\" [attr.x]=\"b.x\" [attr.y]=\"HEADER_UPPER_H\" [attr.width]=\"b.width\" [attr.height]=\"HEADER_LOWER_H\" />\n }\n @for (t of upperTicks(); track t.key) {\n <line class=\"fly-gantt__tick-line\" [attr.x1]=\"t.x\" [attr.y1]=\"0\" [attr.x2]=\"t.x\" [attr.y2]=\"HEADER_H\" />\n <text class=\"fly-gantt__tick-label fly-gantt__tick-label--upper\" [attr.x]=\"t.x + t.width / 2\" [attr.y]=\"HEADER_UPPER_H / 2 + 4\">{{ t.label }}</text>\n }\n @for (t of lowerTicks(); track t.key) {\n <line class=\"fly-gantt__tick-line\" [attr.x1]=\"t.x\" [attr.y1]=\"HEADER_UPPER_H\" [attr.x2]=\"t.x\" [attr.y2]=\"HEADER_H\" />\n <text class=\"fly-gantt__tick-label\" [attr.x]=\"t.x + t.width / 2\" [attr.y]=\"HEADER_UPPER_H + HEADER_LOWER_H / 2 + 4\">{{ t.label }}</text>\n }\n @if (todayX() !== null) {\n <line class=\"fly-gantt__today\" [attr.x1]=\"todayX()\" [attr.y1]=\"HEADER_UPPER_H\" [attr.x2]=\"todayX()\" [attr.y2]=\"HEADER_H\" />\n }\n </svg>\n\n <!-- \u2500\u2500 Label tree (pinned inline-start) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"fly-gantt__labels\" role=\"rowgroup\">\n @for (vm of rowVms(); track vm.flat.row.id) {\n <div\n class=\"fly-gantt__label-row\"\n role=\"row\"\n tabindex=\"-1\"\n [class.fly-gantt__label-row--selected]=\"selectedId() === vm.flat.row.id\"\n [class.fly-gantt__label-row--tinted]=\"vm.tint !== null\"\n [style.height.px]=\"rowHeight()\"\n [style.--fly-gantt-row-tint]=\"vm.tint\"\n [attr.aria-selected]=\"selectedId() === vm.flat.row.id\"\n [attr.aria-label]=\"vm.ariaLabel\"\n (click)=\"onRowClick(vm.flat.row.id)\"\n (dblclick)=\"onRowDblClick(vm.flat.row.id)\"\n (keydown.enter)=\"onLabelRowKeydown($event, vm.flat.row.id)\"\n >\n <span class=\"fly-gantt__label-inner\" [style.padding-inline-start.px]=\"indentFor(vm.flat.depth)\">\n @if (vm.flat.hasChildren) {\n <button\n type=\"button\"\n class=\"fly-gantt__chevron\"\n [class.fly-gantt__chevron--collapsed]=\"isCollapsed(vm.flat.row.id)\"\n [attr.aria-label]=\"(isCollapsed(vm.flat.row.id) ? 'gantt.expand' : 'gantt.collapse') | translate\"\n [attr.aria-expanded]=\"!isCollapsed(vm.flat.row.id)\"\n (click)=\"toggleCollapse(vm.flat.row.id, $event)\"\n >\u25B8</button>\n } @else {\n <span class=\"fly-gantt__chevron-spacer\"></span>\n }\n @if (vm.color) {\n <!-- Colour chip: the row's own colour at full strength, so a milestone stays\n identifiable in the label pane where the band tint is deliberately faint. -->\n <span class=\"fly-gantt__label-swatch\" [style.background]=\"vm.color\" aria-hidden=\"true\"></span>\n }\n <span\n class=\"fly-gantt__label-text\"\n [class.fly-gantt__label-text--group]=\"vm.shape === 'group'\"\n [title]=\"vm.flat.row.label\"\n >{{ vm.flat.row.label }}</span>\n </span>\n </div>\n }\n @if (resizableLabels()) {\n <!-- aria-hidden so this presentational twin of the corner separator is not a second\n (and structurally invalid) child of the rowgroup in the accessibility tree. -->\n <div\n class=\"fly-gantt__splitter fly-gantt__splitter--rail\"\n aria-hidden=\"true\"\n (pointerdown)=\"onLabelResizePointerDown($event)\"\n (dblclick)=\"resetLabelWidth()\"\n ></div>\n }\n </div>\n\n <!-- \u2500\u2500 Time grid body \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <svg\n #bodySvg\n class=\"fly-gantt__body\"\n [attr.width]=\"innerWidth()\"\n [attr.height]=\"bodyHeight()\"\n [attr.viewBox]=\"'0 0 ' + innerWidth() + ' ' + bodyHeight()\"\n >\n <defs>\n <marker\n [attr.id]=\"markerId\"\n markerWidth=\"8\"\n markerHeight=\"8\"\n refX=\"6\"\n refY=\"4\"\n orient=\"auto\"\n markerUnits=\"userSpaceOnUse\"\n >\n <path class=\"fly-gantt__arrowhead\" d=\"M0,0 L7,4 L0,8 Z\" />\n </marker>\n </defs>\n\n <!-- Row colour bands sit at the very bottom of the stack, so the weekend shading and the\n gridlines below still read through them. -->\n @for (vm of tintedRows(); track vm.flat.row.id) {\n <rect\n class=\"fly-gantt__row-band\"\n [attr.x]=\"0\"\n [attr.y]=\"vm.y\"\n [attr.width]=\"innerWidth()\"\n [attr.height]=\"rowHeight()\"\n [style.fill]=\"vm.tint\"\n />\n }\n\n <!-- Weekend shading + vertical gridlines (behind the bars). -->\n @for (b of weekendBands(); track b.key) {\n <rect class=\"fly-gantt__weekend\" [attr.x]=\"b.x\" [attr.y]=\"0\" [attr.width]=\"b.width\" [attr.height]=\"bodyHeight()\" />\n }\n @for (t of lowerTicks(); track t.key) {\n <line class=\"fly-gantt__grid-line\" [attr.x1]=\"t.x\" [attr.y1]=\"0\" [attr.x2]=\"t.x\" [attr.y2]=\"bodyHeight()\" />\n }\n\n <!-- Rows: a full-width hit rect (click + link drop target) then the shape. -->\n @for (vm of rowVms(); track vm.flat.row.id) {\n <g class=\"fly-gantt__row\" [attr.data-row-id]=\"vm.flat.row.id\">\n <rect\n class=\"fly-gantt__row-hit\"\n [class.fly-gantt__row-hit--selected]=\"selectedId() === vm.flat.row.id\"\n [attr.x]=\"0\"\n [attr.y]=\"vm.y\"\n [attr.width]=\"innerWidth()\"\n [attr.height]=\"rowHeight()\"\n (pointerdown)=\"onRowClick(vm.flat.row.id)\"\n (dblclick)=\"onRowDblClick(vm.flat.row.id)\"\n />\n\n @switch (vm.shape) {\n @case ('group') {\n <path class=\"fly-gantt__group\" [attr.d]=\"groupPath(vm)\" [style.fill]=\"vm.color\" [style.stroke]=\"vm.color\" />\n }\n @case ('milestone') {\n <polygon\n class=\"fly-gantt__milestone\"\n [class.fly-gantt__milestone--editable]=\"vm.editable\"\n [attr.points]=\"diamondPoints(vm)\"\n [style.fill]=\"vm.color\"\n (pointerdown)=\"onBarPointerDown($event, vm, 'move')\"\n />\n @if (!readonly() && !vm.flat.row.readonly) {\n <!-- A diamond has no width, so one connector serves it; it reads as the finish\n end, which is what makes a milestone\u2192task drag the FS everyone expects. -->\n <circle\n class=\"fly-gantt__connector\"\n [attr.cx]=\"milestoneConnectorX(vm)\"\n [attr.cy]=\"vm.midY\"\n r=\"4\"\n [attr.aria-label]=\"'gantt.aria.link_handle' | translate: { label: vm.flat.row.label }\"\n (pointerdown)=\"onLinkPointerDown($event, vm, 'finish')\"\n />\n }\n }\n @case ('bar') {\n <g class=\"fly-gantt__bar-group\">\n <rect\n class=\"fly-gantt__bar\"\n [class.fly-gantt__bar--editable]=\"vm.editable\"\n [attr.x]=\"vm.x\"\n [attr.y]=\"barTop(vm)\"\n [attr.width]=\"vm.width\"\n [attr.height]=\"barHeight()\"\n rx=\"4\"\n [style.fill]=\"vm.color\"\n (pointerdown)=\"onBarPointerDown($event, vm, 'move')\"\n />\n @if (vm.progressWidth > 0) {\n <rect\n class=\"fly-gantt__progress\"\n [attr.x]=\"vm.x\"\n [attr.y]=\"barTop(vm)\"\n [attr.width]=\"vm.progressWidth\"\n [attr.height]=\"barHeight()\"\n rx=\"4\"\n />\n }\n @if (vm.editable) {\n <!-- Resize handles at both edges. -->\n <rect\n class=\"fly-gantt__handle\"\n [attr.x]=\"vm.x\"\n [attr.y]=\"barTop(vm)\"\n [attr.width]=\"HANDLE_W\"\n [attr.height]=\"barHeight()\"\n [attr.aria-label]=\"'gantt.aria.resize_start' | translate\"\n (pointerdown)=\"onBarPointerDown($event, vm, 'resize-start')\"\n />\n <rect\n class=\"fly-gantt__handle\"\n [attr.x]=\"vm.x + vm.width - HANDLE_W\"\n [attr.y]=\"barTop(vm)\"\n [attr.width]=\"HANDLE_W\"\n [attr.height]=\"barHeight()\"\n [attr.aria-label]=\"'gantt.aria.resize_end' | translate\"\n (pointerdown)=\"onBarPointerDown($event, vm, 'resize-end')\"\n />\n }\n @if (!readonly() && !vm.flat.row.readonly) {\n <!-- One connector per END. Dragging from the start edge yields an SS/SF link\n and from the finish edge an FS/FF one \u2014 the drop edge picks which. -->\n <circle\n class=\"fly-gantt__connector\"\n [attr.cx]=\"vm.startX\"\n [attr.cy]=\"vm.midY\"\n r=\"4\"\n [attr.aria-label]=\"'gantt.aria.link_handle_start' | translate: { label: vm.flat.row.label }\"\n (pointerdown)=\"onLinkPointerDown($event, vm, 'start')\"\n />\n <circle\n class=\"fly-gantt__connector\"\n [attr.cx]=\"vm.endX\"\n [attr.cy]=\"vm.midY\"\n r=\"4\"\n [attr.aria-label]=\"'gantt.aria.link_handle' | translate: { label: vm.flat.row.label }\"\n (pointerdown)=\"onLinkPointerDown($event, vm, 'finish')\"\n />\n }\n </g>\n }\n }\n </g>\n }\n\n <!-- Dependency arrows (skipped for missing / collapsed endpoints). Each carries a fat\n transparent twin so a 1.5px line is still clickable at pointer precision. -->\n @for (link of linkVms(); track link.key) {\n <g class=\"fly-gantt__link-group\" [class.fly-gantt__link-group--selected]=\"selectedLinkKey() === link.key\">\n <path\n class=\"fly-gantt__link-hit\"\n [attr.d]=\"link.path\"\n [attr.aria-label]=\"link.ariaLabel\"\n (click)=\"onLinkClick($event, link)\"\n />\n <path class=\"fly-gantt__link\" [attr.d]=\"link.path\" [attr.marker-end]=\"'url(#' + markerId + ')'\" />\n @if (selectedLinkKey() === link.key && !readonly()) {\n <g\n class=\"fly-gantt__link-delete\"\n [attr.transform]=\"'translate(' + link.badgeX + ',' + link.badgeY + ')'\"\n [attr.aria-label]=\"'gantt.aria.delete_link' | translate\"\n (click)=\"deleteLink($event, link)\"\n >\n <circle class=\"fly-gantt__link-delete-bg\" r=\"8\" />\n <path class=\"fly-gantt__link-delete-x\" d=\"M-3.5,-3.5 L3.5,3.5 M3.5,-3.5 L-3.5,3.5\" />\n </g>\n }\n </g>\n }\n\n <!-- In-flight link rubber band. -->\n @if (linkGesturePath(); as gp) {\n <path class=\"fly-gantt__link fly-gantt__link--ghost\" [attr.d]=\"gp\" [attr.marker-end]=\"'url(#' + markerId + ')'\" />\n }\n\n <!-- Today line (over the bars). -->\n @if (todayX() !== null) {\n <line class=\"fly-gantt__today\" [attr.x1]=\"todayX()\" [attr.y1]=\"0\" [attr.x2]=\"todayX()\" [attr.y2]=\"bodyHeight()\" />\n }\n </svg>\n </div>\n\n @if (visibleRows().length === 0) {\n <p class=\"fly-gantt__empty\">{{ 'gantt.no_data' | translate }}</p>\n }\n @if (overflowCount() > 0) {\n <p class=\"fly-gantt__overflow\">\n {{ 'gantt.overflow' | translate: { shown: maxRows(), total: maxRows() + overflowCount() } }}\n </p>\n }\n</div>\n\n<!-- Drag candidate tooltip (HTML overlay, follows the cursor). -->\n@if (dragTooltip(); as tip) {\n <div class=\"fly-gantt__tooltip\" [style.left.px]=\"tip.x\" [style.top.px]=\"tip.y\">{{ tip.text }}</div>\n}\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--bg-2, var(--surface-card, #fff));--_surface-alt: var(--bg-3, var(--glass-bg-elevated, #f8fafc));--_surface-hover: var(--bg-hover, var(--surface-hover, #f1f5f9));--_border: var(--w08, var(--surface-border, #e2e8f0));--_border-strong: var(--w1, var(--surface-border, #cbd5e1));--_grid-line: var(--line-3, var(--surface-border, #eef2f7));--_text: var(--ink, var(--text-color, #0f172a));--_text-subtle: var(--ink-3, var(--text-color-secondary, #64748b));--_text-faint: var(--ink-4, var(--text-color-secondary, #94a3b8));--_accent: var(--accent);--_bar: var(--_accent);--_bar-progress: color-mix(in srgb, var(--_accent) 72%, #000);--_weekend: color-mix(in srgb, var(--_text-subtle) 9%, transparent);--_today: var(--danger, #ef4444);--_link: var(--_text-subtle);--_radius: var(--r-lg, 8px);--_transition: var(--t-state, .12s ease);display:block;inline-size:100%;block-size:100%;min-block-size:200px;color:var(--_text);font-size:13px}:host(.fly-gantt--resizing){cursor:col-resize;-webkit-user-select:none;user-select:none}.fly-gantt__scroll{position:relative;inline-size:100%;block-size:100%;overflow:auto;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);box-shadow:var(--shadow-card, none);outline:none}.fly-gantt__scroll:focus-visible{outline:2px solid var(--_accent);outline-offset:-2px}.fly-gantt__canvas{display:grid;position:relative}.fly-gantt__corner{position:sticky;inset-block-start:0;inset-inline-start:0;z-index:4;background:var(--_surface-alt);-webkit-backdrop-filter:blur(12px) saturate(140%);backdrop-filter:blur(12px) saturate(140%);border-inline-end:1px solid var(--_border);border-block-end:1px solid var(--_border-strong)}.fly-gantt__time-header{position:sticky;inset-block-start:0;z-index:3;display:block;background:var(--_surface-alt);-webkit-backdrop-filter:blur(12px) saturate(140%);backdrop-filter:blur(12px) saturate(140%);border-block-end:1px solid var(--_border-strong)}.fly-gantt__tick-line{stroke:var(--_grid-line);stroke-width:1}.fly-gantt__tick-label{fill:var(--_text-subtle);font-size:11px;text-anchor:middle;dominant-baseline:middle}.fly-gantt__tick-label--upper{fill:var(--_text);font-weight:600}.fly-gantt__splitter{position:absolute;inset-block:0;inset-inline-end:-3px;inline-size:7px;z-index:5;cursor:col-resize;touch-action:none}.fly-gantt__splitter:after{content:\"\";position:absolute;inset-block:0;inset-inline-start:3px;inline-size:1px;background:transparent;transition:background var(--_transition)}.fly-gantt__splitter:hover:after,.fly-gantt__splitter:focus-visible:after{background:var(--_accent)}.fly-gantt__splitter:focus-visible{outline:2px solid var(--_accent);outline-offset:-1px}:host(.fly-gantt--resizing) .fly-gantt__splitter:after{background:var(--_accent)}.fly-gantt__labels{position:sticky;inset-inline-start:0;z-index:2;background:var(--_surface);border-inline-end:1px solid var(--_border)}.fly-gantt__label-row{position:relative;display:flex;align-items:center;border-block-end:1px solid var(--_grid-line);cursor:pointer;transition:background var(--_transition)}.fly-gantt__label-row:hover{background:var(--_surface-hover)}.fly-gantt__label-row--selected{background:var(--_surface-hover);box-shadow:inset 3px 0 0 0 var(--_accent)}.fly-gantt__label-row--tinted:before{content:\"\";position:absolute;inset:0;background:var(--fly-gantt-row-tint);pointer-events:none}.fly-gantt__label-inner{position:relative;display:flex;align-items:center;gap:4px;inline-size:100%;min-inline-size:0;padding-inline-end:8px}.fly-gantt__label-swatch{flex:0 0 auto;inline-size:8px;block-size:8px;border-radius:2px}.fly-gantt__chevron{flex:0 0 auto;inline-size:18px;block-size:18px;padding:0;border:none;background:transparent;color:var(--_text-subtle);cursor:pointer;line-height:1;transform:rotate(90deg);transition:transform var(--_transition)}.fly-gantt__chevron--collapsed{transform:rotate(0)}:host(.fly-gantt--rtl) .fly-gantt__chevron--collapsed{transform:rotate(180deg)}.fly-gantt__chevron:focus-visible{outline:2px solid var(--_accent);outline-offset:1px;border-radius:4px}.fly-gantt__chevron-spacer{flex:0 0 auto;inline-size:18px}.fly-gantt__label-text{min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-gantt__label-text--group{font-weight:600}.fly-gantt__body{display:block;touch-action:pan-x pan-y}.fly-gantt__row-band{pointer-events:none}.fly-gantt__weekend{fill:var(--_weekend)}.fly-gantt__grid-line{stroke:var(--_grid-line);stroke-width:1}.fly-gantt__row-hit{fill:transparent}.fly-gantt__row-hit--selected{fill:var(--_surface-hover)}.fly-gantt__bar{fill:var(--_bar);stroke:none}.fly-gantt__bar--editable{cursor:grab}.fly-gantt__progress{fill:var(--_bar-progress);pointer-events:none}.fly-gantt__handle{fill:transparent;cursor:ew-resize}.fly-gantt__connector{fill:var(--_surface);stroke:var(--_accent);stroke-width:1.5;cursor:crosshair;opacity:0;transition:opacity var(--_transition)}.fly-gantt__row:hover .fly-gantt__connector{opacity:1}.fly-gantt__milestone{fill:var(--_accent);stroke:var(--_surface);stroke-width:1}.fly-gantt__milestone--editable{cursor:grab}.fly-gantt__group{fill:var(--_text-subtle);stroke:var(--_text-subtle)}.fly-gantt__link{fill:none;stroke:var(--_link);stroke-width:1.5;pointer-events:none}.fly-gantt__link-hit{fill:none;stroke:transparent;stroke-width:11;pointer-events:stroke;cursor:pointer}.fly-gantt__link-group:hover .fly-gantt__link{stroke:var(--_text)}.fly-gantt__link-group--selected .fly-gantt__link{stroke:var(--_accent);stroke-width:2.5}.fly-gantt__link-delete-bg{fill:var(--_accent)}.fly-gantt__link-delete{cursor:pointer}.fly-gantt__link-delete-x{stroke:var(--_surface);stroke-width:1.75;stroke-linecap:round;fill:none;pointer-events:none}.fly-gantt__link--ghost{stroke-dasharray:4 3;opacity:.8}.fly-gantt__arrowhead{fill:var(--_link)}.fly-gantt__today{stroke:var(--_today);stroke-width:1.5;stroke-dasharray:3 3;pointer-events:none}.fly-gantt__empty,.fly-gantt__overflow{margin:0;padding:12px;color:var(--_text-faint);font-size:12px;text-align:center}.fly-gantt__tooltip{position:fixed;z-index:20;transform:translate(12px,16px);padding:4px 8px;border-radius:6px;background:var(--_text);color:var(--_surface);font-size:12px;white-space:nowrap;pointer-events:none;direction:ltr;unicode-bidi:isolate}\n"] }]
|
|
13843
|
+
}, template: "<!-- Cross-scrolling canvas: a CSS grid whose header row and label column are sticky, so the\r\n time grid scrolls under a pinned header + label tree. All SVG geometry is pre-mirrored for\r\n RTL in the component (see gantt-scale mapX), so this template never branches on direction. -->\r\n<div\r\n class=\"fly-gantt__scroll\"\r\n role=\"grid\"\r\n tabindex=\"0\"\r\n [attr.aria-label]=\"'gantt.aria.grid' | translate\"\r\n [attr.aria-rowcount]=\"visibleRows().length\"\r\n (keydown)=\"onGridKeydown($event)\"\r\n>\r\n <div\r\n class=\"fly-gantt__canvas\"\r\n [style.grid-template-columns]=\"effectiveLabelWidth() + 'px ' + innerWidth() + 'px'\"\r\n [style.grid-template-rows]=\"HEADER_H + 'px ' + bodyHeight() + 'px'\"\r\n >\r\n <!-- \u2500\u2500 Corner (pinned both axes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <div class=\"fly-gantt__corner\" [style.height.px]=\"HEADER_H\">\r\n @if (resizableLabels()) {\r\n <!-- The divider lives in the corner because the corner is the one cell pinned on BOTH\r\n axes: the grip stays reachable however far the user has scrolled. The full-height\r\n strip down the label pane below is the same gesture, presentational only. -->\r\n <div\r\n class=\"fly-gantt__splitter\"\r\n role=\"separator\"\r\n tabindex=\"0\"\r\n aria-orientation=\"vertical\"\r\n [attr.aria-label]=\"'gantt.aria.label_resize' | translate\"\r\n [attr.aria-valuenow]=\"effectiveLabelWidth()\"\r\n [attr.aria-valuemin]=\"MIN_LABEL_W\"\r\n [attr.aria-valuemax]=\"MAX_LABEL_W\"\r\n (pointerdown)=\"onLabelResizePointerDown($event)\"\r\n (keydown)=\"onLabelResizeKeydown($event)\"\r\n (dblclick)=\"resetLabelWidth()\"\r\n ></div>\r\n }\r\n </div>\r\n\r\n <!-- \u2500\u2500 Time header (pinned top) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <svg\r\n class=\"fly-gantt__time-header\"\r\n [attr.width]=\"innerWidth()\"\r\n [attr.height]=\"HEADER_H\"\r\n [attr.viewBox]=\"'0 0 ' + innerWidth() + ' ' + HEADER_H\"\r\n aria-hidden=\"true\"\r\n >\r\n @for (b of weekendBands(); track b.key) {\r\n <rect class=\"fly-gantt__weekend\" [attr.x]=\"b.x\" [attr.y]=\"HEADER_UPPER_H\" [attr.width]=\"b.width\" [attr.height]=\"HEADER_LOWER_H\" />\r\n }\r\n @for (t of upperTicks(); track t.key) {\r\n <line class=\"fly-gantt__tick-line\" [attr.x1]=\"t.x\" [attr.y1]=\"0\" [attr.x2]=\"t.x\" [attr.y2]=\"HEADER_H\" />\r\n <text class=\"fly-gantt__tick-label fly-gantt__tick-label--upper\" [attr.x]=\"t.x + t.width / 2\" [attr.y]=\"HEADER_UPPER_H / 2 + 4\">{{ t.label }}</text>\r\n }\r\n @for (t of lowerTicks(); track t.key) {\r\n <line class=\"fly-gantt__tick-line\" [attr.x1]=\"t.x\" [attr.y1]=\"HEADER_UPPER_H\" [attr.x2]=\"t.x\" [attr.y2]=\"HEADER_H\" />\r\n <text class=\"fly-gantt__tick-label\" [attr.x]=\"t.x + t.width / 2\" [attr.y]=\"HEADER_UPPER_H + HEADER_LOWER_H / 2 + 4\">{{ t.label }}</text>\r\n }\r\n @if (todayX() !== null) {\r\n <line class=\"fly-gantt__today\" [attr.x1]=\"todayX()\" [attr.y1]=\"HEADER_UPPER_H\" [attr.x2]=\"todayX()\" [attr.y2]=\"HEADER_H\" />\r\n }\r\n </svg>\r\n\r\n <!-- \u2500\u2500 Label tree (pinned inline-start) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <div class=\"fly-gantt__labels\" role=\"rowgroup\">\r\n @for (vm of rowVms(); track vm.flat.row.id) {\r\n <div\r\n class=\"fly-gantt__label-row\"\r\n role=\"row\"\r\n tabindex=\"-1\"\r\n [class.fly-gantt__label-row--selected]=\"selectedId() === vm.flat.row.id\"\r\n [class.fly-gantt__label-row--tinted]=\"vm.tint !== null\"\r\n [style.height.px]=\"rowHeight()\"\r\n [style.--fly-gantt-row-tint]=\"vm.tint\"\r\n [attr.aria-selected]=\"selectedId() === vm.flat.row.id\"\r\n [attr.aria-label]=\"vm.ariaLabel\"\r\n (click)=\"onRowClick(vm.flat.row.id)\"\r\n (dblclick)=\"onRowDblClick(vm.flat.row.id)\"\r\n (keydown.enter)=\"onLabelRowKeydown($event, vm.flat.row.id)\"\r\n >\r\n <span class=\"fly-gantt__label-inner\" [style.padding-inline-start.px]=\"indentFor(vm.flat.depth)\">\r\n @if (vm.flat.hasChildren) {\r\n <button\r\n type=\"button\"\r\n class=\"fly-gantt__chevron\"\r\n [class.fly-gantt__chevron--collapsed]=\"isCollapsed(vm.flat.row.id)\"\r\n [attr.aria-label]=\"(isCollapsed(vm.flat.row.id) ? 'gantt.expand' : 'gantt.collapse') | translate\"\r\n [attr.aria-expanded]=\"!isCollapsed(vm.flat.row.id)\"\r\n (click)=\"toggleCollapse(vm.flat.row.id, $event)\"\r\n >\u25B8</button>\r\n } @else {\r\n <span class=\"fly-gantt__chevron-spacer\"></span>\r\n }\r\n @if (vm.color) {\r\n <!-- Colour chip: the row's own colour at full strength, so a milestone stays\r\n identifiable in the label pane where the band tint is deliberately faint. -->\r\n <span class=\"fly-gantt__label-swatch\" [style.background]=\"vm.color\" aria-hidden=\"true\"></span>\r\n }\r\n <span\r\n class=\"fly-gantt__label-text\"\r\n [class.fly-gantt__label-text--group]=\"vm.shape === 'group'\"\r\n [title]=\"vm.flat.row.label\"\r\n >{{ vm.flat.row.label }}</span>\r\n </span>\r\n </div>\r\n }\r\n @if (resizableLabels()) {\r\n <!-- aria-hidden so this presentational twin of the corner separator is not a second\r\n (and structurally invalid) child of the rowgroup in the accessibility tree. -->\r\n <div\r\n class=\"fly-gantt__splitter fly-gantt__splitter--rail\"\r\n aria-hidden=\"true\"\r\n (pointerdown)=\"onLabelResizePointerDown($event)\"\r\n (dblclick)=\"resetLabelWidth()\"\r\n ></div>\r\n }\r\n </div>\r\n\r\n <!-- \u2500\u2500 Time grid body \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\r\n <svg\r\n #bodySvg\r\n class=\"fly-gantt__body\"\r\n [attr.width]=\"innerWidth()\"\r\n [attr.height]=\"bodyHeight()\"\r\n [attr.viewBox]=\"'0 0 ' + innerWidth() + ' ' + bodyHeight()\"\r\n >\r\n <defs>\r\n <marker\r\n [attr.id]=\"markerId\"\r\n markerWidth=\"8\"\r\n markerHeight=\"8\"\r\n refX=\"6\"\r\n refY=\"4\"\r\n orient=\"auto\"\r\n markerUnits=\"userSpaceOnUse\"\r\n >\r\n <path class=\"fly-gantt__arrowhead\" d=\"M0,0 L7,4 L0,8 Z\" />\r\n </marker>\r\n </defs>\r\n\r\n <!-- Row colour bands sit at the very bottom of the stack, so the weekend shading and the\r\n gridlines below still read through them. -->\r\n @for (vm of tintedRows(); track vm.flat.row.id) {\r\n <rect\r\n class=\"fly-gantt__row-band\"\r\n [attr.x]=\"0\"\r\n [attr.y]=\"vm.y\"\r\n [attr.width]=\"innerWidth()\"\r\n [attr.height]=\"rowHeight()\"\r\n [style.fill]=\"vm.tint\"\r\n />\r\n }\r\n\r\n <!-- Weekend shading + vertical gridlines (behind the bars). -->\r\n @for (b of weekendBands(); track b.key) {\r\n <rect class=\"fly-gantt__weekend\" [attr.x]=\"b.x\" [attr.y]=\"0\" [attr.width]=\"b.width\" [attr.height]=\"bodyHeight()\" />\r\n }\r\n @for (t of lowerTicks(); track t.key) {\r\n <line class=\"fly-gantt__grid-line\" [attr.x1]=\"t.x\" [attr.y1]=\"0\" [attr.x2]=\"t.x\" [attr.y2]=\"bodyHeight()\" />\r\n }\r\n\r\n <!-- Rows: a full-width hit rect (click + link drop target) then the shape. -->\r\n @for (vm of rowVms(); track vm.flat.row.id) {\r\n <g class=\"fly-gantt__row\" [attr.data-row-id]=\"vm.flat.row.id\">\r\n <rect\r\n class=\"fly-gantt__row-hit\"\r\n [class.fly-gantt__row-hit--selected]=\"selectedId() === vm.flat.row.id\"\r\n [attr.x]=\"0\"\r\n [attr.y]=\"vm.y\"\r\n [attr.width]=\"innerWidth()\"\r\n [attr.height]=\"rowHeight()\"\r\n (pointerdown)=\"onRowClick(vm.flat.row.id)\"\r\n (dblclick)=\"onRowDblClick(vm.flat.row.id)\"\r\n />\r\n\r\n @switch (vm.shape) {\r\n @case ('group') {\r\n <path class=\"fly-gantt__group\" [attr.d]=\"groupPath(vm)\" [style.fill]=\"vm.color\" [style.stroke]=\"vm.color\" />\r\n }\r\n @case ('milestone') {\r\n <polygon\r\n class=\"fly-gantt__milestone\"\r\n [class.fly-gantt__milestone--editable]=\"vm.editable\"\r\n [attr.points]=\"diamondPoints(vm)\"\r\n [style.fill]=\"vm.color\"\r\n (pointerdown)=\"onBarPointerDown($event, vm, 'move')\"\r\n />\r\n @if (!readonly() && !vm.flat.row.readonly) {\r\n <!-- A diamond has no width, so one connector serves it; it reads as the finish\r\n end, which is what makes a milestone\u2192task drag the FS everyone expects. -->\r\n <circle\r\n class=\"fly-gantt__connector\"\r\n [attr.cx]=\"milestoneConnectorX(vm)\"\r\n [attr.cy]=\"vm.midY\"\r\n r=\"4\"\r\n [attr.aria-label]=\"'gantt.aria.link_handle' | translate: { label: vm.flat.row.label }\"\r\n (pointerdown)=\"onLinkPointerDown($event, vm, 'finish')\"\r\n />\r\n }\r\n }\r\n @case ('bar') {\r\n <g class=\"fly-gantt__bar-group\">\r\n <rect\r\n class=\"fly-gantt__bar\"\r\n [class.fly-gantt__bar--editable]=\"vm.editable\"\r\n [attr.x]=\"vm.x\"\r\n [attr.y]=\"barTop(vm)\"\r\n [attr.width]=\"vm.width\"\r\n [attr.height]=\"barHeight()\"\r\n rx=\"4\"\r\n [style.fill]=\"vm.color\"\r\n (pointerdown)=\"onBarPointerDown($event, vm, 'move')\"\r\n />\r\n @if (vm.progressWidth > 0) {\r\n <rect\r\n class=\"fly-gantt__progress\"\r\n [attr.x]=\"vm.x\"\r\n [attr.y]=\"barTop(vm)\"\r\n [attr.width]=\"vm.progressWidth\"\r\n [attr.height]=\"barHeight()\"\r\n rx=\"4\"\r\n />\r\n }\r\n @if (vm.editable) {\r\n <!-- Resize handles at both edges. -->\r\n <rect\r\n class=\"fly-gantt__handle\"\r\n [attr.x]=\"vm.x\"\r\n [attr.y]=\"barTop(vm)\"\r\n [attr.width]=\"HANDLE_W\"\r\n [attr.height]=\"barHeight()\"\r\n [attr.aria-label]=\"'gantt.aria.resize_start' | translate\"\r\n (pointerdown)=\"onBarPointerDown($event, vm, 'resize-start')\"\r\n />\r\n <rect\r\n class=\"fly-gantt__handle\"\r\n [attr.x]=\"vm.x + vm.width - HANDLE_W\"\r\n [attr.y]=\"barTop(vm)\"\r\n [attr.width]=\"HANDLE_W\"\r\n [attr.height]=\"barHeight()\"\r\n [attr.aria-label]=\"'gantt.aria.resize_end' | translate\"\r\n (pointerdown)=\"onBarPointerDown($event, vm, 'resize-end')\"\r\n />\r\n }\r\n @if (!readonly() && !vm.flat.row.readonly) {\r\n <!-- One connector per END. Dragging from the start edge yields an SS/SF link\r\n and from the finish edge an FS/FF one \u2014 the drop edge picks which. -->\r\n <circle\r\n class=\"fly-gantt__connector\"\r\n [attr.cx]=\"vm.startX\"\r\n [attr.cy]=\"vm.midY\"\r\n r=\"4\"\r\n [attr.aria-label]=\"'gantt.aria.link_handle_start' | translate: { label: vm.flat.row.label }\"\r\n (pointerdown)=\"onLinkPointerDown($event, vm, 'start')\"\r\n />\r\n <circle\r\n class=\"fly-gantt__connector\"\r\n [attr.cx]=\"vm.endX\"\r\n [attr.cy]=\"vm.midY\"\r\n r=\"4\"\r\n [attr.aria-label]=\"'gantt.aria.link_handle' | translate: { label: vm.flat.row.label }\"\r\n (pointerdown)=\"onLinkPointerDown($event, vm, 'finish')\"\r\n />\r\n }\r\n </g>\r\n }\r\n }\r\n </g>\r\n }\r\n\r\n <!-- Dependency arrows (skipped for missing / collapsed endpoints). Each carries a fat\r\n transparent twin so a 1.5px line is still clickable at pointer precision. -->\r\n @for (link of linkVms(); track link.key) {\r\n <g class=\"fly-gantt__link-group\" [class.fly-gantt__link-group--selected]=\"selectedLinkKey() === link.key\">\r\n <path\r\n class=\"fly-gantt__link-hit\"\r\n [attr.d]=\"link.path\"\r\n [attr.aria-label]=\"link.ariaLabel\"\r\n (click)=\"onLinkClick($event, link)\"\r\n />\r\n <path class=\"fly-gantt__link\" [attr.d]=\"link.path\" [attr.marker-end]=\"'url(#' + markerId + ')'\" />\r\n @if (selectedLinkKey() === link.key && !readonly()) {\r\n <g\r\n class=\"fly-gantt__link-delete\"\r\n [attr.transform]=\"'translate(' + link.badgeX + ',' + link.badgeY + ')'\"\r\n [attr.aria-label]=\"'gantt.aria.delete_link' | translate\"\r\n (click)=\"deleteLink($event, link)\"\r\n >\r\n <circle class=\"fly-gantt__link-delete-bg\" r=\"8\" />\r\n <path class=\"fly-gantt__link-delete-x\" d=\"M-3.5,-3.5 L3.5,3.5 M3.5,-3.5 L-3.5,3.5\" />\r\n </g>\r\n }\r\n </g>\r\n }\r\n\r\n <!-- In-flight link rubber band. -->\r\n @if (linkGesturePath(); as gp) {\r\n <path class=\"fly-gantt__link fly-gantt__link--ghost\" [attr.d]=\"gp\" [attr.marker-end]=\"'url(#' + markerId + ')'\" />\r\n }\r\n\r\n <!-- Today line (over the bars). -->\r\n @if (todayX() !== null) {\r\n <line class=\"fly-gantt__today\" [attr.x1]=\"todayX()\" [attr.y1]=\"0\" [attr.x2]=\"todayX()\" [attr.y2]=\"bodyHeight()\" />\r\n }\r\n </svg>\r\n </div>\r\n\r\n @if (visibleRows().length === 0) {\r\n <p class=\"fly-gantt__empty\">{{ 'gantt.no_data' | translate }}</p>\r\n }\r\n @if (overflowCount() > 0) {\r\n <p class=\"fly-gantt__overflow\">\r\n {{ 'gantt.overflow' | translate: { shown: maxRows(), total: maxRows() + overflowCount() } }}\r\n </p>\r\n }\r\n</div>\r\n\r\n<!-- Drag candidate tooltip (HTML overlay, follows the cursor). -->\r\n@if (dragTooltip(); as tip) {\r\n <div class=\"fly-gantt__tooltip\" [style.left.px]=\"tip.x\" [style.top.px]=\"tip.y\">{{ tip.text }}</div>\r\n}\r\n", styles: [":host,:host *,:host *:before,:host *:after{box-sizing:border-box}:host{--_surface: var(--bg-2, var(--surface-card, #fff));--_surface-alt: var(--bg-3, var(--glass-bg-elevated, #f8fafc));--_surface-hover: var(--bg-hover, var(--surface-hover, #f1f5f9));--_border: var(--w08, var(--surface-border, #e2e8f0));--_border-strong: var(--w1, var(--surface-border, #cbd5e1));--_grid-line: var(--line-3, var(--surface-border, #eef2f7));--_text: var(--ink, var(--text-color, #0f172a));--_text-subtle: var(--ink-3, var(--text-color-secondary, #64748b));--_text-faint: var(--ink-4, var(--text-color-secondary, #94a3b8));--_accent: var(--accent);--_bar: var(--_accent);--_bar-progress: color-mix(in srgb, var(--_accent) 72%, #000);--_weekend: color-mix(in srgb, var(--_text-subtle) 9%, transparent);--_today: var(--danger, #ef4444);--_link: var(--_text-subtle);--_radius: var(--r-lg, 8px);--_transition: var(--t-state, .12s ease);display:block;inline-size:100%;block-size:100%;min-block-size:200px;color:var(--_text);font-size:13px}:host(.fly-gantt--resizing){cursor:col-resize;-webkit-user-select:none;user-select:none}.fly-gantt__scroll{position:relative;inline-size:100%;block-size:100%;overflow:auto;border:1px solid var(--_border);border-radius:var(--_radius);background:var(--_surface);box-shadow:var(--shadow-card, none);outline:none}.fly-gantt__scroll:focus-visible{outline:2px solid var(--_accent);outline-offset:-2px}.fly-gantt__canvas{display:grid;position:relative}.fly-gantt__corner{position:sticky;inset-block-start:0;inset-inline-start:0;z-index:4;background:var(--_surface-alt);-webkit-backdrop-filter:blur(12px) saturate(140%);backdrop-filter:blur(12px) saturate(140%);border-inline-end:1px solid var(--_border);border-block-end:1px solid var(--_border-strong)}.fly-gantt__time-header{position:sticky;inset-block-start:0;z-index:3;display:block;background:var(--_surface-alt);-webkit-backdrop-filter:blur(12px) saturate(140%);backdrop-filter:blur(12px) saturate(140%);border-block-end:1px solid var(--_border-strong)}.fly-gantt__tick-line{stroke:var(--_grid-line);stroke-width:1}.fly-gantt__tick-label{fill:var(--_text-subtle);font-size:11px;text-anchor:middle;dominant-baseline:middle}.fly-gantt__tick-label--upper{fill:var(--_text);font-weight:600}.fly-gantt__splitter{position:absolute;inset-block:0;inset-inline-end:-3px;inline-size:7px;z-index:5;cursor:col-resize;touch-action:none}.fly-gantt__splitter:after{content:\"\";position:absolute;inset-block:0;inset-inline-start:3px;inline-size:1px;background:transparent;transition:background var(--_transition)}.fly-gantt__splitter:hover:after,.fly-gantt__splitter:focus-visible:after{background:var(--_accent)}.fly-gantt__splitter:focus-visible{outline:2px solid var(--_accent);outline-offset:-1px}:host(.fly-gantt--resizing) .fly-gantt__splitter:after{background:var(--_accent)}.fly-gantt__labels{position:sticky;inset-inline-start:0;z-index:2;background:var(--_surface);border-inline-end:1px solid var(--_border)}.fly-gantt__label-row{position:relative;display:flex;align-items:center;border-block-end:1px solid var(--_grid-line);cursor:pointer;transition:background var(--_transition)}.fly-gantt__label-row:hover{background:var(--_surface-hover)}.fly-gantt__label-row--selected{background:var(--_surface-hover);box-shadow:inset 3px 0 0 0 var(--_accent)}.fly-gantt__label-row--tinted:before{content:\"\";position:absolute;inset:0;background:var(--fly-gantt-row-tint);pointer-events:none}.fly-gantt__label-inner{position:relative;display:flex;align-items:center;gap:4px;inline-size:100%;min-inline-size:0;padding-inline-end:8px}.fly-gantt__label-swatch{flex:0 0 auto;inline-size:8px;block-size:8px;border-radius:2px}.fly-gantt__chevron{flex:0 0 auto;inline-size:18px;block-size:18px;padding:0;border:none;background:transparent;color:var(--_text-subtle);cursor:pointer;line-height:1;transform:rotate(90deg);transition:transform var(--_transition)}.fly-gantt__chevron--collapsed{transform:rotate(0)}:host(.fly-gantt--rtl) .fly-gantt__chevron--collapsed{transform:rotate(180deg)}.fly-gantt__chevron:focus-visible{outline:2px solid var(--_accent);outline-offset:1px;border-radius:4px}.fly-gantt__chevron-spacer{flex:0 0 auto;inline-size:18px}.fly-gantt__label-text{min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-gantt__label-text--group{font-weight:600}.fly-gantt__body{display:block;touch-action:pan-x pan-y}.fly-gantt__row-band{pointer-events:none}.fly-gantt__weekend{fill:var(--_weekend)}.fly-gantt__grid-line{stroke:var(--_grid-line);stroke-width:1}.fly-gantt__row-hit{fill:transparent}.fly-gantt__row-hit--selected{fill:var(--_surface-hover)}.fly-gantt__bar{fill:var(--_bar);stroke:none}.fly-gantt__bar--editable{cursor:grab}.fly-gantt__progress{fill:var(--_bar-progress);pointer-events:none}.fly-gantt__handle{fill:transparent;cursor:ew-resize}.fly-gantt__connector{fill:var(--_surface);stroke:var(--_accent);stroke-width:1.5;cursor:crosshair;opacity:0;transition:opacity var(--_transition)}.fly-gantt__row:hover .fly-gantt__connector{opacity:1}.fly-gantt__milestone{fill:var(--_accent);stroke:var(--_surface);stroke-width:1}.fly-gantt__milestone--editable{cursor:grab}.fly-gantt__group{fill:var(--_text-subtle);stroke:var(--_text-subtle)}.fly-gantt__link{fill:none;stroke:var(--_link);stroke-width:1.5;pointer-events:none}.fly-gantt__link-hit{fill:none;stroke:transparent;stroke-width:11;pointer-events:stroke;cursor:pointer}.fly-gantt__link-group:hover .fly-gantt__link{stroke:var(--_text)}.fly-gantt__link-group--selected .fly-gantt__link{stroke:var(--_accent);stroke-width:2.5}.fly-gantt__link-delete-bg{fill:var(--_accent)}.fly-gantt__link-delete{cursor:pointer}.fly-gantt__link-delete-x{stroke:var(--_surface);stroke-width:1.75;stroke-linecap:round;fill:none;pointer-events:none}.fly-gantt__link--ghost{stroke-dasharray:4 3;opacity:.8}.fly-gantt__arrowhead{fill:var(--_link)}.fly-gantt__today{stroke:var(--_today);stroke-width:1.5;stroke-dasharray:3 3;pointer-events:none}.fly-gantt__empty,.fly-gantt__overflow{margin:0;padding:12px;color:var(--_text-faint);font-size:12px;text-align:center}.fly-gantt__tooltip{position:fixed;z-index:20;transform:translate(12px,16px);padding:4px 8px;border-radius:6px;background:var(--_text);color:var(--_surface);font-size:12px;white-space:nowrap;pointer-events:none;direction:ltr;unicode-bidi:isolate}\n"] }]
|
|
13556
13844
|
}], propDecorators: { rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], dependencies: [{ type: i0.Input, args: [{ isSignal: true, alias: "dependencies", required: false }] }], zoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoom", required: false }] }], showToday: [{ type: i0.Input, args: [{ isSignal: true, alias: "showToday", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], labelWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelWidth", required: false }] }], resizableLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizableLabels", required: false }] }], rowHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowHeight", required: false }] }], maxRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxRows", required: false }] }], rowDatesChange: [{ type: i0.Output, args: ["rowDatesChange"] }], dependencyCreate: [{ type: i0.Output, args: ["dependencyCreate"] }], dependencyDelete: [{ type: i0.Output, args: ["dependencyDelete"] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], rowDblClick: [{ type: i0.Output, args: ["rowDblClick"] }], labelWidthChange: [{ type: i0.Output, args: ["labelWidthChange"] }], bodyRef: [{ type: i0.ViewChild, args: ['bodySvg', { isSignal: true }] }] } });
|
|
13557
13845
|
|
|
13558
13846
|
// ── Bundled ISO 3166-1 country list ──────────────────────────────────────────
|
|
@@ -16253,7 +16541,7 @@ function projectSearch(search, handlers) {
|
|
|
16253
16541
|
: { filtersActive: search.filtersActive }),
|
|
16254
16542
|
hasFilters: !!search.onOpenFilters,
|
|
16255
16543
|
query: (text) => handlers.search()?.onQuery?.(text),
|
|
16256
|
-
openFilters: () => handlers.search()?.onOpenFilters?.(),
|
|
16544
|
+
openFilters: (trigger) => handlers.search()?.onOpenFilters?.(trigger),
|
|
16257
16545
|
};
|
|
16258
16546
|
}
|
|
16259
16547
|
function projectAction(action, handlers) {
|
|
@@ -16261,7 +16549,7 @@ function projectAction(action, handlers) {
|
|
|
16261
16549
|
return {
|
|
16262
16550
|
...stripHandlers(action),
|
|
16263
16551
|
menu: action.menu ? projectMenu(action, handlers) : null,
|
|
16264
|
-
run: () => handlers.action(id)?.onSelect?.(),
|
|
16552
|
+
run: (trigger) => handlers.action(id)?.onSelect?.(trigger),
|
|
16265
16553
|
};
|
|
16266
16554
|
}
|
|
16267
16555
|
function projectMenu(action, handlers) {
|
|
@@ -17255,7 +17543,11 @@ class FlyMagicActionsComponent {
|
|
|
17255
17543
|
this.toggleMenu(action.id, event.currentTarget);
|
|
17256
17544
|
return;
|
|
17257
17545
|
}
|
|
17258
|
-
action
|
|
17546
|
+
// The button the user pressed, handed to the publisher so an action that opens
|
|
17547
|
+
// an app-owned popover can anchor it to its own trigger — see
|
|
17548
|
+
// `MagicBarActionView.run`. `currentTarget` (not `target`) is the `.fma-btn`
|
|
17549
|
+
// itself rather than whichever `<svg>` node inside it took the pointer.
|
|
17550
|
+
action.run(event.currentTarget);
|
|
17259
17551
|
}
|
|
17260
17552
|
onMenuAction(action, value) {
|
|
17261
17553
|
// Late-bound: `action` is this render's view, but `select()` always resolves
|
|
@@ -19344,6 +19636,29 @@ function flyUnwrapCurrencies(res) {
|
|
|
19344
19636
|
const data = res?.data;
|
|
19345
19637
|
return Array.isArray(data) ? data : [];
|
|
19346
19638
|
}
|
|
19639
|
+
/** The platform endpoint serving the CALLING TENANT's default currency. Relative, like its
|
|
19640
|
+
* catalogue sibling — the host's gateway + auth interceptors apply, and the answer is scoped to
|
|
19641
|
+
* whatever tenant that auth resolves to. */
|
|
19642
|
+
const FLY_CURRENCY_DEFAULT_ENDPOINT = '/api/currencies/default';
|
|
19643
|
+
/**
|
|
19644
|
+
* Unwraps the single-row shape of `GET /api/currencies/default`.
|
|
19645
|
+
*
|
|
19646
|
+
* `null` is a SUCCESS answer, not a failure: a tenant with no country has no default currency,
|
|
19647
|
+
* and a platform-scoped caller has no tenant whose currency could be meant. Anything that is not
|
|
19648
|
+
* recognisably a currency row — an envelope with `data: null`, an error body, an HTML login page
|
|
19649
|
+
* from a misrouted request — collapses to `null` for the same reason
|
|
19650
|
+
* {@link flyUnwrapCurrencies} returns `[]`: a picker must degrade to "no preselection", never
|
|
19651
|
+
* preselect something the platform did not vouch for.
|
|
19652
|
+
*/
|
|
19653
|
+
function flyUnwrapCurrency(res) {
|
|
19654
|
+
const candidate = res !== null && typeof res === 'object' && 'data' in res
|
|
19655
|
+
? res.data
|
|
19656
|
+
: res;
|
|
19657
|
+
if (candidate === null || typeof candidate !== 'object')
|
|
19658
|
+
return null;
|
|
19659
|
+
const row = candidate;
|
|
19660
|
+
return typeof row.code === 'string' && row.code.length > 0 ? candidate : null;
|
|
19661
|
+
}
|
|
19347
19662
|
/**
|
|
19348
19663
|
* **`FlyCurrencyCatalogService`** — the one ISO-code → {@link FlyCurrency} adapter for the
|
|
19349
19664
|
* platform currency catalogue (`GET /api/currencies/brief`).
|
|
@@ -19464,6 +19779,8 @@ let _flyCurrencySelectorUid = 0;
|
|
|
19464
19779
|
* Re-exported from {@link FlyCurrencyCatalogService}'s module so the picker and the display-only
|
|
19465
19780
|
* catalogue cannot drift on which route serves the rows. */
|
|
19466
19781
|
const CURRENCIES_BRIEF_ENDPOINT = FLY_CURRENCIES_BRIEF_ENDPOINT;
|
|
19782
|
+
/** The platform endpoint behind {@link FlyCurrencySelectorComponent.applyTenantDefault}. */
|
|
19783
|
+
const CURRENCY_DEFAULT_ENDPOINT = FLY_CURRENCY_DEFAULT_ENDPOINT;
|
|
19467
19784
|
/** Cached answer of {@link probeFlagEmojiSupport} — the measurement is stable for a document's lifetime. */
|
|
19468
19785
|
let _flagEmojiSupport = null;
|
|
19469
19786
|
/**
|
|
@@ -19574,6 +19891,30 @@ class FlyCurrencySelectorComponent {
|
|
|
19574
19891
|
currencies = input(null, ...(ngDevMode ? [{ debugName: "currencies" }] : /* istanbul ignore next */ []));
|
|
19575
19892
|
/** Host-supplied loader, used when `[currencies]` is absent. Falls back to `GET /api/currencies/brief`. */
|
|
19576
19893
|
fetchFn = input(null, ...(ngDevMode ? [{ debugName: "fetchFn" }] : /* istanbul ignore next */ []));
|
|
19894
|
+
/**
|
|
19895
|
+
* Pre-fill an EMPTY single-select with the calling tenant's default currency
|
|
19896
|
+
* (`GET /api/currencies/default`, or `[tenantDefaultFetchFn]`). On by default.
|
|
19897
|
+
*
|
|
19898
|
+
* An amount is nearly always denominated in the tenant's reporting currency, and a form that
|
|
19899
|
+
* opens empty makes every user restate a fact the platform already holds — while an untouched
|
|
19900
|
+
* field silently saves null, which downstream money formatting cannot render at all.
|
|
19901
|
+
*
|
|
19902
|
+
* Deliberately narrow. It applies only when ALL of these hold, because outside them a
|
|
19903
|
+
* preselection would be a guess rather than a default:
|
|
19904
|
+
* - `mode="single"` — a multi-select accumulates a SET, and seeding one member of a set
|
|
19905
|
+
* states something the tenant currency does not say.
|
|
19906
|
+
* - not `locked()` — a frozen value is authoritative; proposing one contradicts the lock.
|
|
19907
|
+
* - the control is still empty — a written value always wins.
|
|
19908
|
+
*
|
|
19909
|
+
* It fires at most once per instance: clearing the field by hand latches it off, so the value
|
|
19910
|
+
* cannot reappear and read as the clear having not registered.
|
|
19911
|
+
*
|
|
19912
|
+
* A host that supplied `[currencies]` or `[fetchFn]` is never taken to the platform endpoint —
|
|
19913
|
+
* see {@link tenantDefaultFetchFn}, which is how such a host opts back in.
|
|
19914
|
+
*/
|
|
19915
|
+
applyTenantDefault = input(true, ...(ngDevMode ? [{ debugName: "applyTenantDefault" }] : /* istanbul ignore next */ []));
|
|
19916
|
+
/** Host-supplied loader for {@link applyTenantDefault}. Falls back to `GET /api/currencies/default`. */
|
|
19917
|
+
tenantDefaultFetchFn = input(null, ...(ngDevMode ? [{ debugName: "tenantDefaultFetchFn" }] : /* istanbul ignore next */ []));
|
|
19577
19918
|
/** Restrict the offered set to these ISO 4217 codes (case-insensitive). Empty = offer everything. */
|
|
19578
19919
|
allowedCodes = input([], ...(ngDevMode ? [{ debugName: "allowedCodes" }] : /* istanbul ignore next */ []));
|
|
19579
19920
|
/**
|
|
@@ -19634,6 +19975,12 @@ class FlyCurrencySelectorComponent {
|
|
|
19634
19975
|
_selectedCodes = signal([], ...(ngDevMode ? [{ debugName: "_selectedCodes" }] : /* istanbul ignore next */ []));
|
|
19635
19976
|
_cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "_cvaDisabled" }] : /* istanbul ignore next */ []));
|
|
19636
19977
|
_fetchStarted = false;
|
|
19978
|
+
/** Guards the once-per-instance tenant-default resolve. */
|
|
19979
|
+
_tenantDefaultStarted = false;
|
|
19980
|
+
/** Latches once the default has been applied, or ruled out by any commit (pick, clear, write). */
|
|
19981
|
+
_tenantDefaultSettled = false;
|
|
19982
|
+
/** The resolved row, kept so a `writeValue(null)` arriving AFTER the fetch can still use it. */
|
|
19983
|
+
_tenantDefaultRow = null;
|
|
19637
19984
|
searchEl;
|
|
19638
19985
|
triggerEl;
|
|
19639
19986
|
_onChange = () => undefined;
|
|
@@ -19649,6 +19996,34 @@ class FlyCurrencySelectorComponent {
|
|
|
19649
19996
|
this._fetchStarted = true;
|
|
19650
19997
|
this._load();
|
|
19651
19998
|
});
|
|
19999
|
+
// A control that HOLDS a value must resolve it, whether or not the user ever opens the
|
|
20000
|
+
// panel: `selected()` maps codes through the loaded rows, so a saved code with no rows
|
|
20001
|
+
// renders as the placeholder — an edit form reading "Select a currency" over a project that
|
|
20002
|
+
// is denominated in AED. The lazy rule is still intact for the case it was written for: an
|
|
20003
|
+
// EMPTY selector on a tab nobody opens fetches nothing.
|
|
20004
|
+
effect(() => {
|
|
20005
|
+
if (this._fetchStarted)
|
|
20006
|
+
return;
|
|
20007
|
+
if (this.currencies() !== null)
|
|
20008
|
+
return;
|
|
20009
|
+
if (this._selectedCodes().length === 0)
|
|
20010
|
+
return;
|
|
20011
|
+
this._fetchStarted = true;
|
|
20012
|
+
this._load();
|
|
20013
|
+
});
|
|
20014
|
+
// The tenant default is resolved EAGERLY, unlike the catalogue: its whole job is to be in
|
|
20015
|
+
// place before the user looks at the field, so deferring it to first open would defeat it.
|
|
20016
|
+
// The cost is bounded - one small request per defaulting selector, against an endpoint that
|
|
20017
|
+
// returns a single row. An effect rather than the constructor body so `applyTenantDefault`
|
|
20018
|
+
// and `locked` are read AFTER the host's bindings are applied, not at their declared values.
|
|
20019
|
+
effect(() => {
|
|
20020
|
+
if (this._tenantDefaultStarted)
|
|
20021
|
+
return;
|
|
20022
|
+
if (!this.applyTenantDefault() || this.isMulti() || this.locked())
|
|
20023
|
+
return;
|
|
20024
|
+
this._tenantDefaultStarted = true;
|
|
20025
|
+
this._loadTenantDefault();
|
|
20026
|
+
});
|
|
19652
20027
|
}
|
|
19653
20028
|
// ── Derived view state ─────────────────────────────────────────────────────
|
|
19654
20029
|
effectiveDisabled = computed(() => this.disabled() || this._cvaDisabled(), ...(ngDevMode ? [{ debugName: "effectiveDisabled" }] : /* istanbul ignore next */ []));
|
|
@@ -19873,13 +20248,69 @@ class FlyCurrencySelectorComponent {
|
|
|
19873
20248
|
this._loaded.set(rows);
|
|
19874
20249
|
});
|
|
19875
20250
|
}
|
|
20251
|
+
_loadTenantDefault() {
|
|
20252
|
+
const fetch = this.tenantDefaultFetchFn();
|
|
20253
|
+
// A host that took over the CATALOGUE owns the default too: `[currencies]`/`[fetchFn]` are
|
|
20254
|
+
// the "do not reach the platform routes" declaration (offline builds, fixtures, apps that
|
|
20255
|
+
// proxy reference data), and honouring it for the rows while quietly firing a platform
|
|
20256
|
+
// request for the default would break exactly the consumers those inputs exist for. Such a
|
|
20257
|
+
// host opts back in by supplying `[tenantDefaultFetchFn]`.
|
|
20258
|
+
const hostOwnsData = this.currencies() !== null || this.fetchFn() !== null;
|
|
20259
|
+
const source = fetch
|
|
20260
|
+
? fetch()
|
|
20261
|
+
: this.http && !hostOwnsData
|
|
20262
|
+
? this.http.get(CURRENCY_DEFAULT_ENDPOINT).pipe(map(flyUnwrapCurrency))
|
|
20263
|
+
: null;
|
|
20264
|
+
// No transport and no loader is not an error state here: an offline surface simply has no
|
|
20265
|
+
// default. Nothing is surfaced to the user - unlike a failed CATALOGUE load, which must be
|
|
20266
|
+
// retryable because it leaves the control unusable.
|
|
20267
|
+
if (!source)
|
|
20268
|
+
return;
|
|
20269
|
+
source.pipe(catchError$1(() => of(null))).subscribe((row) => {
|
|
20270
|
+
this._tenantDefaultRow = row;
|
|
20271
|
+
this._applyTenantDefaultIfEmpty();
|
|
20272
|
+
});
|
|
20273
|
+
}
|
|
20274
|
+
/**
|
|
20275
|
+
* Applies the resolved default to a still-empty control.
|
|
20276
|
+
*
|
|
20277
|
+
* Called from both ends of a race with no fixed winner: the fetch may land before the forms
|
|
20278
|
+
* layer writes, or after it writes the empty value a form is built with.
|
|
20279
|
+
*/
|
|
20280
|
+
_applyTenantDefaultIfEmpty() {
|
|
20281
|
+
if (this._tenantDefaultSettled)
|
|
20282
|
+
return;
|
|
20283
|
+
if (!this.applyTenantDefault() || this.isMulti() || this.locked())
|
|
20284
|
+
return;
|
|
20285
|
+
const row = this._tenantDefaultRow;
|
|
20286
|
+
if (!row)
|
|
20287
|
+
return;
|
|
20288
|
+
if (this._selectedCodes().length > 0)
|
|
20289
|
+
return;
|
|
20290
|
+
// Seed the row so the trigger can RENDER the pick straight away. Without this the selection
|
|
20291
|
+
// would be invisible until the catalogue loads - which is lazy, on first open - so the field
|
|
20292
|
+
// would look untouched while its form value said otherwise. `_load` replaces the whole set
|
|
20293
|
+
// on success, and leaves this seed alone on failure.
|
|
20294
|
+
if (this.currencies() === null && this._loaded().length === 0)
|
|
20295
|
+
this._loaded.set([row]);
|
|
20296
|
+
this._commit([row.code]);
|
|
20297
|
+
}
|
|
19876
20298
|
// ── ControlValueAccessor ───────────────────────────────────────────────────
|
|
19877
20299
|
writeValue(value) {
|
|
19878
20300
|
if (value === null || value === undefined) {
|
|
19879
20301
|
this._selectedCodes.set([]);
|
|
20302
|
+
// An empty write re-tries the default - this is the common `patchValue`-after-load path,
|
|
20303
|
+
// and on a fresh form it is the write that arrives BEFORE the fetch has landed.
|
|
20304
|
+
this._applyTenantDefaultIfEmpty();
|
|
19880
20305
|
return;
|
|
19881
20306
|
}
|
|
19882
|
-
|
|
20307
|
+
const codes = typeof value === 'string' ? [value] : [...value];
|
|
20308
|
+
this._selectedCodes.set(codes);
|
|
20309
|
+
// A written value is the host's own answer; there is no hole left for a default to fill.
|
|
20310
|
+
if (codes.length > 0)
|
|
20311
|
+
this._tenantDefaultSettled = true;
|
|
20312
|
+
else
|
|
20313
|
+
this._applyTenantDefaultIfEmpty();
|
|
19883
20314
|
}
|
|
19884
20315
|
registerOnChange(fn) {
|
|
19885
20316
|
this._onChange = fn;
|
|
@@ -19896,6 +20327,10 @@ class FlyCurrencySelectorComponent {
|
|
|
19896
20327
|
return this.isMulti() ? [...codes] : (codes[0] ?? null);
|
|
19897
20328
|
}
|
|
19898
20329
|
_commit(codes) {
|
|
20330
|
+
// Every user-driven change routes through here - pick, toggle, remove, clear - so this is the
|
|
20331
|
+
// one place that has to latch the default off. A cleared field that refilled itself would be
|
|
20332
|
+
// indistinguishable from the clear never having registered.
|
|
20333
|
+
this._tenantDefaultSettled = true;
|
|
19899
20334
|
this._selectedCodes.set([...codes]);
|
|
19900
20335
|
this._onChange(this._controlValue());
|
|
19901
20336
|
this.selectionChange.emit([...codes]);
|
|
@@ -19905,13 +20340,13 @@ class FlyCurrencySelectorComponent {
|
|
|
19905
20340
|
this._onTouched();
|
|
19906
20341
|
}
|
|
19907
20342
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyCurrencySelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
19908
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyCurrencySelectorComponent, isStandalone: true, selector: "fly-currency-selector", inputs: { mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, currencies: { classPropertyName: "currencies", publicName: "currencies", isSignal: true, isRequired: false, transformFunction: null }, fetchFn: { classPropertyName: "fetchFn", publicName: "fetchFn", isSignal: true, isRequired: false, transformFunction: null }, allowedCodes: { classPropertyName: "allowedCodes", publicName: "allowedCodes", isSignal: true, isRequired: false, transformFunction: null }, pinnedCodes: { classPropertyName: "pinnedCodes", publicName: "pinnedCodes", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, locked: { classPropertyName: "locked", publicName: "locked", isSignal: true, isRequired: false, transformFunction: null }, lockedReasonKey: { classPropertyName: "lockedReasonKey", publicName: "lockedReasonKey", isSignal: true, isRequired: false, transformFunction: null }, lockedReasonParams: { classPropertyName: "lockedReasonParams", publicName: "lockedReasonParams", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange", selectionDetailChange: "selectionDetailChange", openedChange: "openedChange" }, host: { properties: { "class.fly-currency-selector--open": "isOpen()", "class.fly-currency-selector--disabled": "effectiveDisabled()", "class.fly-currency-selector--locked": "locked()" }, classAttribute: "fly-currency-selector" }, providers: [
|
|
20343
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyCurrencySelectorComponent, isStandalone: true, selector: "fly-currency-selector", inputs: { mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, currencies: { classPropertyName: "currencies", publicName: "currencies", isSignal: true, isRequired: false, transformFunction: null }, fetchFn: { classPropertyName: "fetchFn", publicName: "fetchFn", isSignal: true, isRequired: false, transformFunction: null }, applyTenantDefault: { classPropertyName: "applyTenantDefault", publicName: "applyTenantDefault", isSignal: true, isRequired: false, transformFunction: null }, tenantDefaultFetchFn: { classPropertyName: "tenantDefaultFetchFn", publicName: "tenantDefaultFetchFn", isSignal: true, isRequired: false, transformFunction: null }, allowedCodes: { classPropertyName: "allowedCodes", publicName: "allowedCodes", isSignal: true, isRequired: false, transformFunction: null }, pinnedCodes: { classPropertyName: "pinnedCodes", publicName: "pinnedCodes", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, locked: { classPropertyName: "locked", publicName: "locked", isSignal: true, isRequired: false, transformFunction: null }, lockedReasonKey: { classPropertyName: "lockedReasonKey", publicName: "lockedReasonKey", isSignal: true, isRequired: false, transformFunction: null }, lockedReasonParams: { classPropertyName: "lockedReasonParams", publicName: "lockedReasonParams", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange", selectionDetailChange: "selectionDetailChange", openedChange: "openedChange" }, host: { properties: { "class.fly-currency-selector--open": "isOpen()", "class.fly-currency-selector--disabled": "effectiveDisabled()", "class.fly-currency-selector--locked": "locked()" }, classAttribute: "fly-currency-selector" }, providers: [
|
|
19909
20344
|
{
|
|
19910
20345
|
provide: NG_VALUE_ACCESSOR,
|
|
19911
20346
|
useExisting: forwardRef(() => FlyCurrencySelectorComponent),
|
|
19912
20347
|
multi: true,
|
|
19913
20348
|
},
|
|
19914
|
-
], viewQueries: [{ propertyName: "searchEl", first: true, predicate: ["searchRef"], descendants: true }, { propertyName: "triggerEl", first: true, predicate: ["triggerRef"], descendants: true }], ngImport: i0, template: "<div\r\n class=\"fly-currency-selector__wrap\"\r\n [flyClickOutsideEnabled]=\"isOpen()\"\r\n (flyClickOutside)=\"close()\"\r\n>\r\n @if (locked()) {\r\n <!-- \u2500\u2500 Locked readout \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n NOT a dimmed `disabled` trigger: there is no dropdown affordance here at all \u2014 the\r\n value is frozen on purpose, not merely unavailable right now. `role=\"group\"` +\r\n `aria-label` name the field the way the combobox trigger's `aria-label` does; the\r\n reason is REAL, always-rendered text (not a `title` attribute, which many screen\r\n readers never announce and which no sighted user sees without hovering) and is\r\n additionally wired via `aria-describedby` for AT that reads by relationship rather\r\n than document order. -->\r\n <div\r\n class=\"fly-currency-selector__locked\"\r\n role=\"group\"\r\n [attr.aria-label]=\"ariaLabel() || placeholderText()\"\r\n [attr.aria-describedby]=\"lockedReasonId\"\r\n >\r\n @if (selected().length === 0) {\r\n <span class=\"fly-currency-selector__placeholder\">{{ placeholderText() }}</span>\r\n } @else {\r\n <span class=\"fly-currency-selector__chips\">\r\n @for (c of selected(); track c.code) {\r\n <span class=\"fly-currency-selector__chip\">\r\n <span class=\"fly-currency-selector__tile\" aria-hidden=\"true\">\r\n @if (flagsRenderable() && c.flag) {\r\n <span class=\"fly-currency-selector__tile-flag\">{{ c.flag }}</span>\r\n } @else {\r\n <span class=\"fly-currency-selector__tile-code\">{{ tileCode(c) }}</span>\r\n }\r\n </span>\r\n <span class=\"fly-currency-selector__chip-code\">{{ c.code }}</span>\r\n <span class=\"fly-currency-selector__chip-symbol\" aria-hidden=\"true\">{{ c.symbol }}</span>\r\n </span>\r\n }\r\n </span>\r\n }\r\n <span class=\"fly-currency-selector__lock-icon\" aria-hidden=\"true\">🔒</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 });
|
|
20349
|
+
], 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 });
|
|
19915
20350
|
}
|
|
19916
20351
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyCurrencySelectorComponent, decorators: [{
|
|
19917
20352
|
type: Component,
|
|
@@ -19927,7 +20362,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
|
|
|
19927
20362
|
'[class.fly-currency-selector--disabled]': 'effectiveDisabled()',
|
|
19928
20363
|
'[class.fly-currency-selector--locked]': 'locked()',
|
|
19929
20364
|
}, template: "<div\r\n class=\"fly-currency-selector__wrap\"\r\n [flyClickOutsideEnabled]=\"isOpen()\"\r\n (flyClickOutside)=\"close()\"\r\n>\r\n @if (locked()) {\r\n <!-- \u2500\u2500 Locked readout \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n NOT a dimmed `disabled` trigger: there is no dropdown affordance here at all \u2014 the\r\n value is frozen on purpose, not merely unavailable right now. `role=\"group\"` +\r\n `aria-label` name the field the way the combobox trigger's `aria-label` does; the\r\n reason is REAL, always-rendered text (not a `title` attribute, which many screen\r\n readers never announce and which no sighted user sees without hovering) and is\r\n additionally wired via `aria-describedby` for AT that reads by relationship rather\r\n than document order. -->\r\n <div\r\n class=\"fly-currency-selector__locked\"\r\n role=\"group\"\r\n [attr.aria-label]=\"ariaLabel() || placeholderText()\"\r\n [attr.aria-describedby]=\"lockedReasonId\"\r\n >\r\n @if (selected().length === 0) {\r\n <span class=\"fly-currency-selector__placeholder\">{{ placeholderText() }}</span>\r\n } @else {\r\n <span class=\"fly-currency-selector__chips\">\r\n @for (c of selected(); track c.code) {\r\n <span class=\"fly-currency-selector__chip\">\r\n <span class=\"fly-currency-selector__tile\" aria-hidden=\"true\">\r\n @if (flagsRenderable() && c.flag) {\r\n <span class=\"fly-currency-selector__tile-flag\">{{ c.flag }}</span>\r\n } @else {\r\n <span class=\"fly-currency-selector__tile-code\">{{ tileCode(c) }}</span>\r\n }\r\n </span>\r\n <span class=\"fly-currency-selector__chip-code\">{{ c.code }}</span>\r\n <span class=\"fly-currency-selector__chip-symbol\" aria-hidden=\"true\">{{ c.symbol }}</span>\r\n </span>\r\n }\r\n </span>\r\n }\r\n <span class=\"fly-currency-selector__lock-icon\" aria-hidden=\"true\">🔒</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"] }]
|
|
19930
|
-
}], ctorParameters: () => [], propDecorators: { mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], currencies: [{ type: i0.Input, args: [{ isSignal: true, alias: "currencies", required: false }] }], fetchFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "fetchFn", required: false }] }], allowedCodes: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowedCodes", required: false }] }], pinnedCodes: [{ type: i0.Input, args: [{ isSignal: true, alias: "pinnedCodes", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], locked: [{ type: i0.Input, args: [{ isSignal: true, alias: "locked", required: false }] }], lockedReasonKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "lockedReasonKey", required: false }] }], lockedReasonParams: [{ type: i0.Input, args: [{ isSignal: true, alias: "lockedReasonParams", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], selectionDetailChange: [{ type: i0.Output, args: ["selectionDetailChange"] }], openedChange: [{ type: i0.Output, args: ["openedChange"] }], searchEl: [{
|
|
20365
|
+
}], ctorParameters: () => [], propDecorators: { mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], currencies: [{ type: i0.Input, args: [{ isSignal: true, alias: "currencies", required: false }] }], fetchFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "fetchFn", required: false }] }], applyTenantDefault: [{ type: i0.Input, args: [{ isSignal: true, alias: "applyTenantDefault", required: false }] }], tenantDefaultFetchFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "tenantDefaultFetchFn", required: false }] }], allowedCodes: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowedCodes", required: false }] }], pinnedCodes: [{ type: i0.Input, args: [{ isSignal: true, alias: "pinnedCodes", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], locked: [{ type: i0.Input, args: [{ isSignal: true, alias: "locked", required: false }] }], lockedReasonKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "lockedReasonKey", required: false }] }], lockedReasonParams: [{ type: i0.Input, args: [{ isSignal: true, alias: "lockedReasonParams", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], selectionDetailChange: [{ type: i0.Output, args: ["selectionDetailChange"] }], openedChange: [{ type: i0.Output, args: ["openedChange"] }], searchEl: [{
|
|
19931
20366
|
type: ViewChild,
|
|
19932
20367
|
args: ['searchRef']
|
|
19933
20368
|
}], triggerEl: [{
|
|
@@ -21785,14 +22220,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
|
|
|
21785
22220
|
args: [{ selector: 'fly-meta', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<dl>\r\n @for (item of items(); track $index) {\r\n <div>\r\n <dt>{{ item.labelKey | translate }}</dt>\r\n <dd [class.mono]=\"item.mono ?? false\">{{ item.value }}</dd>\r\n </div>\r\n }\r\n <ng-content />\r\n</dl>\r\n", styles: [":host{display:block}dl{margin:0;display:flex;flex-direction:column;gap:10px}dl ::ng-deep>div{display:grid;grid-template-columns:96px 1fr;gap:var(--sp-3);align-items:baseline;font-size:var(--text-sm)}dl ::ng-deep dt{margin:0;font-size:10.5px;font-weight:var(--fw-medium);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-4)}dl ::ng-deep dd{margin:0;min-width:0;color:var(--ink);word-break:break-word;line-height:1.45}dl ::ng-deep dd.mono,dl ::ng-deep dd .mono{font-size:var(--text-2xs)}\n"] }]
|
|
21786
22221
|
}], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }] } });
|
|
21787
22222
|
|
|
21788
|
-
function canGoPrev(page) {
|
|
22223
|
+
function canGoPrev$1(page) {
|
|
21789
22224
|
return page > 1;
|
|
21790
22225
|
}
|
|
21791
|
-
function canGoNext(page, totalPages) {
|
|
22226
|
+
function canGoNext$1(page, totalPages) {
|
|
21792
22227
|
return page < totalPages;
|
|
21793
22228
|
}
|
|
21794
22229
|
/** Clamp a requested page into `[1, max(1, totalPages)]`. */
|
|
21795
|
-
function clampPage(page, totalPages) {
|
|
22230
|
+
function clampPage$1(page, totalPages) {
|
|
21796
22231
|
return Math.min(Math.max(1, page), Math.max(1, totalPages));
|
|
21797
22232
|
}
|
|
21798
22233
|
|
|
@@ -21827,10 +22262,10 @@ class FlyPaginationComponent {
|
|
|
21827
22262
|
showEdges = input(false, ...(ngDevMode ? [{ debugName: "showEdges" }] : /* istanbul ignore next */ []));
|
|
21828
22263
|
/** Emits the target page (already clamped to bounds). */
|
|
21829
22264
|
pageChange = output();
|
|
21830
|
-
prevEnabled = computed(() => canGoPrev(this.page()), ...(ngDevMode ? [{ debugName: "prevEnabled" }] : /* istanbul ignore next */ []));
|
|
21831
|
-
nextEnabled = computed(() => canGoNext(this.page(), this.totalPages()), ...(ngDevMode ? [{ debugName: "nextEnabled" }] : /* istanbul ignore next */ []));
|
|
22265
|
+
prevEnabled = computed(() => canGoPrev$1(this.page()), ...(ngDevMode ? [{ debugName: "prevEnabled" }] : /* istanbul ignore next */ []));
|
|
22266
|
+
nextEnabled = computed(() => canGoNext$1(this.page(), this.totalPages()), ...(ngDevMode ? [{ debugName: "nextEnabled" }] : /* istanbul ignore next */ []));
|
|
21832
22267
|
goTo(target) {
|
|
21833
|
-
const next = clampPage(target, this.totalPages());
|
|
22268
|
+
const next = clampPage$1(target, this.totalPages());
|
|
21834
22269
|
if (next !== this.page())
|
|
21835
22270
|
this.pageChange.emit(next);
|
|
21836
22271
|
}
|
|
@@ -21842,6 +22277,54 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
|
|
|
21842
22277
|
args: [{ selector: 'fly-pagination', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<nav class=\"pg\" [attr.aria-label]=\"'ui.pagination.label' | translate\">\r\n @if (rangeText(); as range) {\r\n <span class=\"pg__range\">{{ range }}</span>\r\n }\r\n <div class=\"pg__controls\">\r\n @if (showEdges()) {\r\n <button\r\n type=\"button\"\r\n class=\"pg__btn\"\r\n [disabled]=\"!prevEnabled()\"\r\n (click)=\"goTo(1)\"\r\n [attr.aria-label]=\"'ui.pagination.first' | translate\"\r\n ><span class=\"pg__chev\" aria-hidden=\"true\">\u00AB</span></button>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"pg__btn\"\r\n [disabled]=\"!prevEnabled()\"\r\n (click)=\"goTo(page() - 1)\"\r\n [attr.aria-label]=\"'ui.pagination.previous' | translate\"\r\n >\r\n <svg class=\"pg__arrow\" data-arrow=\"prev\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <polyline points=\"15 6 9 12 15 18\" />\r\n </svg>\r\n </button>\r\n <span class=\"pg__current\" aria-live=\"polite\">\r\n {{ 'ui.pagination.pageOf' | translate: { page: page(), total: totalPages() } }}\r\n </span>\r\n <button\r\n type=\"button\"\r\n class=\"pg__btn\"\r\n [disabled]=\"!nextEnabled()\"\r\n (click)=\"goTo(page() + 1)\"\r\n [attr.aria-label]=\"'ui.pagination.next' | translate\"\r\n >\r\n <svg class=\"pg__arrow\" data-arrow=\"next\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <polyline points=\"9 6 15 12 9 18\" />\r\n </svg>\r\n </button>\r\n @if (showEdges()) {\r\n <button\r\n type=\"button\"\r\n class=\"pg__btn\"\r\n [disabled]=\"!nextEnabled()\"\r\n (click)=\"goTo(totalPages())\"\r\n [attr.aria-label]=\"'ui.pagination.last' | translate\"\r\n ><span class=\"pg__chev\" aria-hidden=\"true\">\u00BB</span></button>\r\n }\r\n </div>\r\n</nav>\r\n", styles: [".pg{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-3);margin-block-start:var(--sp-4);font-size:var(--text-sm);color:var(--ink-3)}.pg__range{font-size:var(--text-sm);color:var(--ink-3);direction:ltr;unicode-bidi:isolate}.pg__range b{color:var(--ink);font-weight:var(--fw-medium)}.pg__controls{display:flex;align-items:center;gap:2px;margin-inline-start:auto;font-size:var(--text-sm)}.pg__btn{inline-size:26px;block-size:26px;padding:0;border:1px solid transparent;background:transparent;color:var(--ink-3);border-radius:50%;font-size:var(--text-sm);cursor:pointer;display:inline-grid;place-items:center;transition:background var(--t-state),color var(--t-state)}.pg__btn:hover:not(:disabled){background:var(--w06);color:var(--ink)}.pg__btn:disabled{color:var(--w2);cursor:default}.pg__btn:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.pg__arrow{inline-size:14px;block-size:14px}:host-context([dir=rtl]) .pg__arrow{transform:scaleX(-1)}.pg__current{font-size:var(--text-sm);font-weight:var(--fw-semibold);font-variant-numeric:tabular-nums;padding:0 var(--sp-3);background:var(--accent);border:1px solid transparent;border-radius:999px;color:var(--on-accent-fill);height:24px;display:inline-grid;place-items:center;direction:ltr;unicode-bidi:isolate}:host-context(.rtl) .pg__chev,:host-context([dir=rtl]) .pg__chev{display:inline-block;transform:scaleX(-1)}\n"] }]
|
|
21843
22278
|
}], propDecorators: { page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: true }] }], totalPages: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalPages", required: true }] }], rangeText: [{ type: i0.Input, args: [{ isSignal: true, alias: "rangeText", required: false }] }], showEdges: [{ type: i0.Input, args: [{ isSignal: true, alias: "showEdges", required: false }] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }] } });
|
|
21844
22279
|
|
|
22280
|
+
/** 0-indexed sibling of `pagination.logic.ts` — see `pager.component.ts` for why. */
|
|
22281
|
+
function canGoPrev(page) {
|
|
22282
|
+
return page > 0;
|
|
22283
|
+
}
|
|
22284
|
+
function canGoNext(page, totalPages) {
|
|
22285
|
+
return page < totalPages - 1;
|
|
22286
|
+
}
|
|
22287
|
+
/** Clamp a requested page into `[0, max(0, totalPages - 1)]`. */
|
|
22288
|
+
function clampPage(page, totalPages) {
|
|
22289
|
+
return Math.min(Math.max(0, page), Math.max(0, totalPages - 1));
|
|
22290
|
+
}
|
|
22291
|
+
|
|
22292
|
+
/**
|
|
22293
|
+
* Numbered list pager — back arrow, one pill per page, forward arrow — for the
|
|
22294
|
+
* lists that want every page as a target rather than `fly-pagination`'s
|
|
22295
|
+
* «prev / page of total / next» band. Shares the list's surface and continues
|
|
22296
|
+
* its border (see `COMPONENTS.md`): pair with a list container that sets
|
|
22297
|
+
* `border-bottom: 0` so the two read as one panel.
|
|
22298
|
+
*
|
|
22299
|
+
* <fly-pager [page]="page()" [totalPages]="totalPages()" (goToPage)="goTo($event)" />
|
|
22300
|
+
*
|
|
22301
|
+
* `page`/the emitted target are 0-indexed (array-index pagination); buttons
|
|
22302
|
+
* display `page + 1`. This is the opposite convention from `fly-pagination`
|
|
22303
|
+
* (1-indexed) — the two are separate components for separate list shapes, not
|
|
22304
|
+
* a shared base, so pick the one matching how the caller already indexes its
|
|
22305
|
+
* pages rather than converting at the call site.
|
|
22306
|
+
*/
|
|
22307
|
+
class FlyPagerComponent {
|
|
22308
|
+
page = input.required(...(ngDevMode ? [{ debugName: "page" }] : /* istanbul ignore next */ []));
|
|
22309
|
+
totalPages = input.required(...(ngDevMode ? [{ debugName: "totalPages" }] : /* istanbul ignore next */ []));
|
|
22310
|
+
/** Emits the target page (already clamped to bounds), 0-indexed. */
|
|
22311
|
+
goToPage = output();
|
|
22312
|
+
prevEnabled = computed(() => canGoPrev(this.page()), ...(ngDevMode ? [{ debugName: "prevEnabled" }] : /* istanbul ignore next */ []));
|
|
22313
|
+
nextEnabled = computed(() => canGoNext(this.page(), this.totalPages()), ...(ngDevMode ? [{ debugName: "nextEnabled" }] : /* istanbul ignore next */ []));
|
|
22314
|
+
pages = computed(() => Array.from({ length: Math.max(0, this.totalPages()) }, (_, i) => i), ...(ngDevMode ? [{ debugName: "pages" }] : /* istanbul ignore next */ []));
|
|
22315
|
+
goTo(target) {
|
|
22316
|
+
const next = clampPage(target, this.totalPages());
|
|
22317
|
+
if (next !== this.page())
|
|
22318
|
+
this.goToPage.emit(next);
|
|
22319
|
+
}
|
|
22320
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyPagerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
22321
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyPagerComponent, isStandalone: true, selector: "fly-pager", inputs: { page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: true, transformFunction: null }, totalPages: { classPropertyName: "totalPages", publicName: "totalPages", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { goToPage: "goToPage" }, ngImport: i0, template: "@if (totalPages() > 1) {\r\n <nav class=\"pgr\" [attr.aria-label]=\"'ui.pagination.label' | translate\">\r\n <button\r\n type=\"button\"\r\n class=\"pgr__arrow\"\r\n data-arrow=\"back\"\r\n [disabled]=\"!prevEnabled()\"\r\n (click)=\"goTo(page() - 1)\"\r\n [attr.aria-label]=\"'ui.pager.previous' | translate\"\r\n >\r\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <path d=\"M14.8 5.2L8 12l6.8 6.8\" />\r\n </svg>\r\n </button>\r\n @for (p of pages(); track p) {\r\n <button\r\n type=\"button\"\r\n class=\"pgr__page\"\r\n [class.pgr__page--current]=\"p === page()\"\r\n [attr.aria-current]=\"p === page() ? 'page' : null\"\r\n (click)=\"goTo(p)\"\r\n [attr.aria-label]=\"'ui.pager.page' | translate: { page: p + 1 }\"\r\n >{{ p + 1 }}</button>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"pgr__arrow\"\r\n data-arrow=\"forward\"\r\n [disabled]=\"!nextEnabled()\"\r\n (click)=\"goTo(page() + 1)\"\r\n [attr.aria-label]=\"'ui.pager.next' | translate\"\r\n >\r\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <path d=\"M9.2 5.2L16 12l-6.8 6.8\" />\r\n </svg>\r\n </button>\r\n </nav>\r\n}\r\n", styles: [".pgr{flex:none;display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:4px;padding:14px 8px;background:var(--w03);border:1px solid var(--w08);border-top:0}.pgr__arrow{flex:none;inline-size:26px;block-size:26px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--w6);cursor:pointer;display:grid;place-items:center;transition:background var(--t-state),color var(--t-state)}.pgr__arrow svg{inline-size:14px;block-size:14px}.pgr__arrow:hover:not(:disabled){background:var(--w06)}.pgr__arrow:disabled{color:var(--w2);cursor:default}.pgr__arrow:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}:host-context([dir=rtl]) .pgr__arrow[data-arrow] svg{transform:scaleX(-1)}.pgr__page{flex:none;min-inline-size:24px;block-size:24px;padding:0 3px;border:0;border-radius:50%;background:transparent;color:var(--w45);cursor:pointer;font-family:inherit;font-size:12px;font-weight:600;line-height:1;font-variant-numeric:tabular-nums;transition:background var(--t-state),color var(--t-state)}.pgr__page:hover{background:var(--w08)}.pgr__page--current{background:var(--accent);color:var(--on-accent-fill)}.pgr__page--current:hover{background:var(--accent)}.pgr__page:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
22322
|
+
}
|
|
22323
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyPagerComponent, decorators: [{
|
|
22324
|
+
type: Component,
|
|
22325
|
+
args: [{ selector: 'fly-pager', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (totalPages() > 1) {\r\n <nav class=\"pgr\" [attr.aria-label]=\"'ui.pagination.label' | translate\">\r\n <button\r\n type=\"button\"\r\n class=\"pgr__arrow\"\r\n data-arrow=\"back\"\r\n [disabled]=\"!prevEnabled()\"\r\n (click)=\"goTo(page() - 1)\"\r\n [attr.aria-label]=\"'ui.pager.previous' | translate\"\r\n >\r\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <path d=\"M14.8 5.2L8 12l6.8 6.8\" />\r\n </svg>\r\n </button>\r\n @for (p of pages(); track p) {\r\n <button\r\n type=\"button\"\r\n class=\"pgr__page\"\r\n [class.pgr__page--current]=\"p === page()\"\r\n [attr.aria-current]=\"p === page() ? 'page' : null\"\r\n (click)=\"goTo(p)\"\r\n [attr.aria-label]=\"'ui.pager.page' | translate: { page: p + 1 }\"\r\n >{{ p + 1 }}</button>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"pgr__arrow\"\r\n data-arrow=\"forward\"\r\n [disabled]=\"!nextEnabled()\"\r\n (click)=\"goTo(page() + 1)\"\r\n [attr.aria-label]=\"'ui.pager.next' | translate\"\r\n >\r\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <path d=\"M9.2 5.2L16 12l-6.8 6.8\" />\r\n </svg>\r\n </button>\r\n </nav>\r\n}\r\n", styles: [".pgr{flex:none;display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:4px;padding:14px 8px;background:var(--w03);border:1px solid var(--w08);border-top:0}.pgr__arrow{flex:none;inline-size:26px;block-size:26px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--w6);cursor:pointer;display:grid;place-items:center;transition:background var(--t-state),color var(--t-state)}.pgr__arrow svg{inline-size:14px;block-size:14px}.pgr__arrow:hover:not(:disabled){background:var(--w06)}.pgr__arrow:disabled{color:var(--w2);cursor:default}.pgr__arrow:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}:host-context([dir=rtl]) .pgr__arrow[data-arrow] svg{transform:scaleX(-1)}.pgr__page{flex:none;min-inline-size:24px;block-size:24px;padding:0 3px;border:0;border-radius:50%;background:transparent;color:var(--w45);cursor:pointer;font-family:inherit;font-size:12px;font-weight:600;line-height:1;font-variant-numeric:tabular-nums;transition:background var(--t-state),color var(--t-state)}.pgr__page:hover{background:var(--w08)}.pgr__page--current{background:var(--accent);color:var(--on-accent-fill)}.pgr__page--current:hover{background:var(--accent)}.pgr__page:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}\n"] }]
|
|
22326
|
+
}], propDecorators: { page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: true }] }], totalPages: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalPages", required: true }] }], goToPage: [{ type: i0.Output, args: ["goToPage"] }] } });
|
|
22327
|
+
|
|
21845
22328
|
/**
|
|
21846
22329
|
* Listing card shell — one anatomy for the three reference card patterns:
|
|
21847
22330
|
*
|
|
@@ -22104,6 +22587,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
|
|
|
22104
22587
|
}]
|
|
22105
22588
|
}] });
|
|
22106
22589
|
|
|
22590
|
+
/** Gap between the trigger and a floating panel, and the breathing room kept at a viewport edge. */
|
|
22591
|
+
const ANCHOR_GAP = 10;
|
|
22592
|
+
const EDGE_PAD = 16;
|
|
22593
|
+
/**
|
|
22594
|
+
* How far the panel's trailing edge overhangs the trigger's, matching the canon
|
|
22595
|
+
* dialog's `right: -4px`. The panel is many times wider than the button, so
|
|
22596
|
+
* aligning the two edges exactly reads as a near-miss; the deliberate overhang
|
|
22597
|
+
* reads as intentional.
|
|
22598
|
+
*/
|
|
22599
|
+
const ANCHOR_OVERHANG = 4;
|
|
22107
22600
|
/**
|
|
22108
22601
|
* Collapsible advanced-filter panel — structural port of the reference
|
|
22109
22602
|
* `.signals-page-fp` block (head row + optional status section + field area +
|
|
@@ -22128,6 +22621,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
|
|
|
22128
22621
|
* - Deferred pages set `showSearchButton` true, drop the per-field handlers,
|
|
22129
22622
|
* and commit staged criteria on `(search)`.
|
|
22130
22623
|
*
|
|
22624
|
+
* ## Three placements, and how to pick one
|
|
22625
|
+
* - **In flow** (neither `anchored` nor `anchorEl`) — a block in the page. Opening
|
|
22626
|
+
* it pushes the content below it down. Only right for a page whose filters are
|
|
22627
|
+
* part of its permanent furniture.
|
|
22628
|
+
* - **`[anchored]`** — a dropdown pinned to the trailing edge of a `position:
|
|
22629
|
+
* relative` ancestor the page supplies (the wrapper around its search box).
|
|
22630
|
+
* - **`[anchorEl]`** — a dropdown pinned to a specific ELEMENT anywhere on screen,
|
|
22631
|
+
* including one outside this component's own DOM subtree. See {@link anchorEl}.
|
|
22632
|
+
*
|
|
22633
|
+
* `anchorEl` wins when both are set, so a page can bind it optionally and fall
|
|
22634
|
+
* back to `anchored` in whichever mode has no element to offer.
|
|
22635
|
+
*
|
|
22131
22636
|
* Matching the source, the panel renders nothing while `open` is false.
|
|
22132
22637
|
*/
|
|
22133
22638
|
class FlyFilterPanelComponent {
|
|
@@ -22146,18 +22651,66 @@ class FlyFilterPanelComponent {
|
|
|
22146
22651
|
* `[expandable]`.
|
|
22147
22652
|
*/
|
|
22148
22653
|
anchored = input(false, ...(ngDevMode ? [{ debugName: "anchored" }] : /* istanbul ignore next */ []));
|
|
22654
|
+
/**
|
|
22655
|
+
* The element this panel hangs off — a dropdown pinned under a specific trigger
|
|
22656
|
+
* rather than under whichever ancestor happens to be positioned.
|
|
22657
|
+
*
|
|
22658
|
+
* ## The case it exists for
|
|
22659
|
+
* A listing screen's filter toggle does not necessarily live in the screen.
|
|
22660
|
+
* Embedded in the desktop shell, the toggle is a magic-bar action painted by
|
|
22661
|
+
* `fly-magic-actions` in the top-bar pill — the shell's DOM, above the window,
|
|
22662
|
+
* across a federation boundary. `[anchored]` cannot reach it: it pins to a
|
|
22663
|
+
* positioned ancestor, and the nearest one is inside the app's own page header.
|
|
22664
|
+
* PPM's projects register shipped exactly that way, and the result was a panel
|
|
22665
|
+
* floating mid-window while the button that opened it sat in the chrome above,
|
|
22666
|
+
* with nothing tying the two together. `MagicBarActionView.run(trigger)` hands
|
|
22667
|
+
* the publisher that button; binding it here is the other half.
|
|
22668
|
+
*
|
|
22669
|
+
* ## What changes when it is set
|
|
22670
|
+
* The host is **portalled to `<body>`** and switched to `position: fixed`. Both
|
|
22671
|
+
* are forced rather than chosen: a shell window establishes a containing block
|
|
22672
|
+
* (`transform` / `backdrop-filter`) and clips its content box, so a panel left in
|
|
22673
|
+
* the app's subtree resolves `fixed` against the window instead of the viewport
|
|
22674
|
+
* AND is clipped at the window's top edge — which is exactly where a panel
|
|
22675
|
+
* hanging off the chrome above it needs to paint. Placement is then measured from
|
|
22676
|
+
* the anchor's rect on every open, resize and scroll, mirrored under RTL, and
|
|
22677
|
+
* clamped so a trigger near a viewport edge still opens fully on screen.
|
|
22678
|
+
*
|
|
22679
|
+
* ## Lifetime
|
|
22680
|
+
* `null` (the default) restores the in-flow/`anchored` behaviour, and the host
|
|
22681
|
+
* returns to where it was declared. Bind it to a signal your open-state clears —
|
|
22682
|
+
* the element the magic bar hands you is the button AS PAINTED, and a re-publish
|
|
22683
|
+
* or a mode switch destroys it.
|
|
22684
|
+
*/
|
|
22685
|
+
anchorEl = input(null, ...(ngDevMode ? [{ debugName: "anchorEl" }] : /* istanbul ignore next */ []));
|
|
22149
22686
|
/** Clear-filters pressed — the page resets its own filter state. */
|
|
22150
22687
|
cleared = output();
|
|
22151
|
-
/** Close (×) pressed — the page flips its open flag. */
|
|
22688
|
+
/** Close (×) pressed, or a dismissing press outside — the page flips its open flag. */
|
|
22152
22689
|
closed = output();
|
|
22153
22690
|
/** Search pressed — deferred pages commit staged criteria and query. */
|
|
22154
22691
|
searched = output();
|
|
22155
22692
|
/** Whether the `[filter-more]` advanced fields are revealed. */
|
|
22156
22693
|
showMore = signal(false, ...(ngDevMode ? [{ debugName: "showMore" }] : /* istanbul ignore next */ []));
|
|
22694
|
+
/** True while the panel is a `<body>`-portalled, anchor-positioned dropdown. */
|
|
22695
|
+
floating = computed(() => this.open() && this.anchorEl() !== null, ...(ngDevMode ? [{ debugName: "floating" }] : /* istanbul ignore next */ []));
|
|
22696
|
+
/**
|
|
22697
|
+
* Whether a press outside the panel should dismiss it.
|
|
22698
|
+
*
|
|
22699
|
+
* Only while the panel FLOATS over the page. In flow it is a block OF the page,
|
|
22700
|
+
* and closing it because the user clicked the table it filters would discard
|
|
22701
|
+
* staged criteria on an unrelated interaction. Floating, the opposite holds: an
|
|
22702
|
+
* overlay that survives a click elsewhere is the "panel will not collapse on
|
|
22703
|
+
* click-away" bug.
|
|
22704
|
+
*/
|
|
22705
|
+
dismissable = computed(() => this.open() && (this.anchored() || this.floating()), ...(ngDevMode ? [{ debugName: "dismissable" }] : /* istanbul ignore next */ []));
|
|
22157
22706
|
_host = inject(ElementRef);
|
|
22707
|
+
_doc = inject(DOCUMENT$1);
|
|
22158
22708
|
_destroyRef = inject(DestroyRef);
|
|
22159
22709
|
_clipObserver = null;
|
|
22160
22710
|
_placeRaf = 0;
|
|
22711
|
+
/** Marks where the host was declared, so un-portalling puts it back exactly. */
|
|
22712
|
+
_portalHome = null;
|
|
22713
|
+
_floatTeardown = null;
|
|
22161
22714
|
constructor() {
|
|
22162
22715
|
// Anchored placement: the CSS pins the panel's inline-end to the anchor
|
|
22163
22716
|
// wrapper, but the wrapper is usually a small search box that can sit
|
|
@@ -22168,10 +22721,22 @@ class FlyFilterPanelComponent {
|
|
|
22168
22721
|
// with a physical translate + width clamp so the panel always stays inside,
|
|
22169
22722
|
// whichever side of the window the anchor is on. Physical-pixel deltas are
|
|
22170
22723
|
// direction-agnostic, so the same clamp holds under RTL.
|
|
22724
|
+
//
|
|
22725
|
+
// Floating placement is the same idea against a different box — the viewport,
|
|
22726
|
+
// and an anchor that need not be anywhere near this component's subtree.
|
|
22171
22727
|
afterRenderEffect(() => {
|
|
22172
|
-
const
|
|
22728
|
+
const floating = this.floating();
|
|
22729
|
+
const anchored = this.anchored() && this.open();
|
|
22173
22730
|
untracked(() => {
|
|
22174
|
-
if (
|
|
22731
|
+
if (floating) {
|
|
22732
|
+
this._teardownPlacement();
|
|
22733
|
+
this._portal();
|
|
22734
|
+
this._placeFloating();
|
|
22735
|
+
this._observeFloating();
|
|
22736
|
+
return;
|
|
22737
|
+
}
|
|
22738
|
+
this._teardownFloating();
|
|
22739
|
+
if (!anchored) {
|
|
22175
22740
|
this._teardownPlacement();
|
|
22176
22741
|
return;
|
|
22177
22742
|
}
|
|
@@ -22179,8 +22744,124 @@ class FlyFilterPanelComponent {
|
|
|
22179
22744
|
this._observeClipAncestor();
|
|
22180
22745
|
});
|
|
22181
22746
|
});
|
|
22182
|
-
this._destroyRef.onDestroy(() =>
|
|
22747
|
+
this._destroyRef.onDestroy(() => {
|
|
22748
|
+
this._teardownPlacement();
|
|
22749
|
+
this._teardownFloating();
|
|
22750
|
+
});
|
|
22751
|
+
}
|
|
22752
|
+
// ── Floating (anchorEl) placement ───────────────────────────────────────────
|
|
22753
|
+
/**
|
|
22754
|
+
* Move the host to `<body>`, leaving a comment node where it was declared.
|
|
22755
|
+
*
|
|
22756
|
+
* The comment is what makes this reversible. Angular keeps rendering into the
|
|
22757
|
+
* host wherever it sits — relocating a DOM node does not detach a view — but the
|
|
22758
|
+
* component cannot ask the DOM "where were you?" after the fact, and the original
|
|
22759
|
+
* parent may itself be torn down while the panel is open. Un-portalling therefore
|
|
22760
|
+
* re-inserts before the marker while the marker is still attached, and simply
|
|
22761
|
+
* leaves the host where it is otherwise.
|
|
22762
|
+
*/
|
|
22763
|
+
_portal() {
|
|
22764
|
+
const host = this._host.nativeElement;
|
|
22765
|
+
const body = this._doc.body;
|
|
22766
|
+
if (!body || host.parentNode === body)
|
|
22767
|
+
return;
|
|
22768
|
+
if (!this._portalHome) {
|
|
22769
|
+
this._portalHome = this._doc.createComment('fly-filter-panel');
|
|
22770
|
+
host.parentNode?.insertBefore(this._portalHome, host);
|
|
22771
|
+
}
|
|
22772
|
+
body.appendChild(host);
|
|
22773
|
+
}
|
|
22774
|
+
_unportal() {
|
|
22775
|
+
const host = this._host.nativeElement;
|
|
22776
|
+
const home = this._portalHome;
|
|
22777
|
+
this._portalHome = null;
|
|
22778
|
+
if (home?.parentNode) {
|
|
22779
|
+
home.parentNode.insertBefore(host, home);
|
|
22780
|
+
home.parentNode.removeChild(home);
|
|
22781
|
+
return;
|
|
22782
|
+
}
|
|
22783
|
+
// Nowhere to go home to: the declaring parent was torn down while the panel was
|
|
22784
|
+
// open (a route change, a closing window). Angular has already destroyed the view,
|
|
22785
|
+
// but the host is OUR node in `<body>` and nothing else will collect it — leaving it
|
|
22786
|
+
// there strands an empty panel element in the document for the rest of the session.
|
|
22787
|
+
if (host.parentNode === this._doc.body)
|
|
22788
|
+
this._doc.body.removeChild(host);
|
|
22789
|
+
}
|
|
22790
|
+
/**
|
|
22791
|
+
* Pin the panel under {@link anchorEl}: {@link ANCHOR_GAP} below its bottom edge,
|
|
22792
|
+
* its trailing edge overhanging the anchor's by {@link ANCHOR_OVERHANG}, clamped
|
|
22793
|
+
* into the viewport on both axes.
|
|
22794
|
+
*
|
|
22795
|
+
* Trailing edge, not "right": the anchor's OWN computed direction decides which
|
|
22796
|
+
* physical side that is, so the panel opens inward from the trigger under both LTR
|
|
22797
|
+
* and RTL with nothing passed by the caller. The final clamp is in physical pixels
|
|
22798
|
+
* and therefore direction-agnostic — a trigger 30px from the viewport edge yields
|
|
22799
|
+
* the same on-screen result either way.
|
|
22800
|
+
*/
|
|
22801
|
+
_placeFloating() {
|
|
22802
|
+
const host = this._host.nativeElement;
|
|
22803
|
+
const anchor = this.anchorEl();
|
|
22804
|
+
const win = host.ownerDocument.defaultView;
|
|
22805
|
+
if (!anchor || !win)
|
|
22806
|
+
return;
|
|
22807
|
+
const rect = anchor.getBoundingClientRect();
|
|
22808
|
+
const vw = win.innerWidth;
|
|
22809
|
+
const vh = win.innerHeight;
|
|
22810
|
+
// Clamp the width FIRST: the host's own rect below has to reflect it, and a
|
|
22811
|
+
// panel wider than the viewport makes every horizontal clamp meaningless.
|
|
22812
|
+
host.style.setProperty('--fp-max-w', `${Math.max(240, Math.round(vw - 2 * EDGE_PAD))}px`);
|
|
22813
|
+
const top = Math.min(rect.bottom + ANCHOR_GAP, Math.max(EDGE_PAD, vh - EDGE_PAD));
|
|
22814
|
+
host.style.setProperty('--fp-y', `${Math.round(top)}px`);
|
|
22815
|
+
host.style.setProperty('--fp-max-h', `${Math.max(160, Math.round(vh - top - EDGE_PAD))}px`);
|
|
22816
|
+
// Read AFTER the width clamp and the block start are applied, so the measured
|
|
22817
|
+
// box is the final one.
|
|
22818
|
+
const width = host.getBoundingClientRect().width;
|
|
22819
|
+
const rtl = win.getComputedStyle(anchor).direction === 'rtl';
|
|
22820
|
+
const raw = rtl ? rect.left - ANCHOR_OVERHANG : rect.right + ANCHOR_OVERHANG - width;
|
|
22821
|
+
const maxLeft = Math.max(EDGE_PAD, vw - EDGE_PAD - width);
|
|
22822
|
+
host.style.setProperty('--fp-x', `${Math.round(Math.min(Math.max(raw, EDGE_PAD), maxLeft))}px`);
|
|
22823
|
+
}
|
|
22824
|
+
/**
|
|
22825
|
+
* Re-place on anything that can move the anchor under an already-open panel: the
|
|
22826
|
+
* viewport resizing, the shell window being dragged or resized (which moves or
|
|
22827
|
+
* resizes the anchor's own box), and scrolling ANYWHERE — captured, because the
|
|
22828
|
+
* anchor lives in the chrome and the scroller that shifts it is not necessarily an
|
|
22829
|
+
* ancestor of this host, which is now in `<body>`.
|
|
22830
|
+
*/
|
|
22831
|
+
_observeFloating() {
|
|
22832
|
+
if (this._floatTeardown)
|
|
22833
|
+
return;
|
|
22834
|
+
const host = this._host.nativeElement;
|
|
22835
|
+
const win = host.ownerDocument.defaultView;
|
|
22836
|
+
const anchor = this.anchorEl();
|
|
22837
|
+
if (!win)
|
|
22838
|
+
return;
|
|
22839
|
+
const replace = () => {
|
|
22840
|
+
cancelAnimationFrame(this._placeRaf);
|
|
22841
|
+
this._placeRaf = requestAnimationFrame(() => this._placeFloating());
|
|
22842
|
+
};
|
|
22843
|
+
win.addEventListener('resize', replace);
|
|
22844
|
+
win.addEventListener('scroll', replace, true);
|
|
22845
|
+
const observer = anchor && typeof ResizeObserver !== 'undefined' ? new ResizeObserver(replace) : null;
|
|
22846
|
+
if (anchor && observer)
|
|
22847
|
+
observer.observe(anchor);
|
|
22848
|
+
this._floatTeardown = () => {
|
|
22849
|
+
win.removeEventListener('resize', replace);
|
|
22850
|
+
win.removeEventListener('scroll', replace, true);
|
|
22851
|
+
observer?.disconnect();
|
|
22852
|
+
cancelAnimationFrame(this._placeRaf);
|
|
22853
|
+
};
|
|
22854
|
+
}
|
|
22855
|
+
_teardownFloating() {
|
|
22856
|
+
this._floatTeardown?.();
|
|
22857
|
+
this._floatTeardown = null;
|
|
22858
|
+
this._unportal();
|
|
22859
|
+
const host = this._host.nativeElement;
|
|
22860
|
+
for (const prop of ['--fp-x', '--fp-y', '--fp-max-h', '--fp-max-w']) {
|
|
22861
|
+
host.style.removeProperty(prop);
|
|
22862
|
+
}
|
|
22183
22863
|
}
|
|
22864
|
+
// ── Anchored (positioned-ancestor) placement ────────────────────────────────
|
|
22184
22865
|
_place() {
|
|
22185
22866
|
const host = this._host.nativeElement;
|
|
22186
22867
|
const win = host.ownerDocument.defaultView;
|
|
@@ -22191,14 +22872,13 @@ class FlyFilterPanelComponent {
|
|
|
22191
22872
|
host.style.removeProperty('--fp-max-w');
|
|
22192
22873
|
const clipRect = this._clipAncestor(host)?.getBoundingClientRect();
|
|
22193
22874
|
const bounds = clipRect ?? new DOMRect(0, 0, win.innerWidth, win.innerHeight);
|
|
22194
|
-
const
|
|
22195
|
-
const availableWidth = Math.max(240, bounds.width - 2 * pad);
|
|
22875
|
+
const availableWidth = Math.max(240, bounds.width - 2 * EDGE_PAD);
|
|
22196
22876
|
host.style.setProperty('--fp-max-w', `${Math.round(availableWidth)}px`);
|
|
22197
22877
|
// Setting the width clamp above forces layout before this read, so the rect
|
|
22198
22878
|
// already reflects the clamped size.
|
|
22199
22879
|
const rect = host.getBoundingClientRect();
|
|
22200
|
-
const minLeft = bounds.left +
|
|
22201
|
-
const maxRight = bounds.right -
|
|
22880
|
+
const minLeft = bounds.left + EDGE_PAD;
|
|
22881
|
+
const maxRight = bounds.right - EDGE_PAD;
|
|
22202
22882
|
let shift = 0;
|
|
22203
22883
|
if (rect.left < minLeft)
|
|
22204
22884
|
shift = minLeft - rect.left;
|
|
@@ -22241,12 +22921,15 @@ class FlyFilterPanelComponent {
|
|
|
22241
22921
|
host.style.removeProperty('--fp-max-w');
|
|
22242
22922
|
}
|
|
22243
22923
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyFilterPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
22244
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyFilterPanelComponent, isStandalone: true, selector: "fly-filter-panel", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: true, transformFunction: null }, titleKey: { classPropertyName: "titleKey", publicName: "titleKey", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, hasMore: { classPropertyName: "hasMore", publicName: "hasMore", isSignal: true, isRequired: false, transformFunction: null }, showSearchButton: { classPropertyName: "showSearchButton", publicName: "showSearchButton", isSignal: true, isRequired: false, transformFunction: null }, anchored: { classPropertyName: "anchored", publicName: "anchored", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cleared: "cleared", closed: "closed", searched: "searched" }, host: { properties: { "class.fp--anchored": "anchored()" } }, ngImport: i0, template: " @if (open()) {\r\n <div
|
|
22924
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyFilterPanelComponent, isStandalone: true, selector: "fly-filter-panel", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: true, transformFunction: null }, titleKey: { classPropertyName: "titleKey", publicName: "titleKey", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, hasMore: { classPropertyName: "hasMore", publicName: "hasMore", isSignal: true, isRequired: false, transformFunction: null }, showSearchButton: { classPropertyName: "showSearchButton", publicName: "showSearchButton", isSignal: true, isRequired: false, transformFunction: null }, anchored: { classPropertyName: "anchored", publicName: "anchored", isSignal: true, isRequired: false, transformFunction: null }, anchorEl: { classPropertyName: "anchorEl", publicName: "anchorEl", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cleared: "cleared", closed: "closed", searched: "searched" }, host: { properties: { "class.fp--anchored": "anchored() && !floating()", "class.fp--floating": "floating()" } }, ngImport: i0, template: " @if (open()) {\r\n <!--\r\n The dismiss host is `.fp`, and the TRIGGER is listed as ignored rather than\r\n wrapped: an anchored panel's toggle can live in another component entirely (the\r\n shell's magic bar), so there is no element that contains both. See the\r\n directive's `flyClickOutsideIgnore` for what goes wrong without it.\r\n -->\r\n <div\r\n class=\"fp\"\r\n [attr.data-layout]=\"layout()\"\r\n [flyClickOutsideEnabled]=\"dismissable()\"\r\n [flyClickOutsideIgnore]=\"anchorEl()\"\r\n (flyClickOutside)=\"closed.emit()\"\r\n >\r\n <div class=\"fp__head\">\r\n <div class=\"fp__title\">{{ titleKey() | translate }}</div>\r\n <div class=\"fp__actions\">\r\n <button type=\"button\" class=\"fp__clear\" (click)=\"cleared.emit()\">\r\n {{ 'ui.action.clearFilters' | translate }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fp__close\"\r\n (click)=\"closed.emit()\"\r\n [attr.aria-label]=\"'ui.action.close' | translate\"\r\n >\u00D7</button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"fp__body\">\r\n <ng-content select=\"[filter-status]\" />\r\n\r\n <div class=\"fp__grid\">\r\n <ng-content />\r\n </div>\r\n\r\n @if (hasMore()) {\r\n <div class=\"fp__more\" [hidden]=\"!showMore()\">\r\n <div class=\"fp__grid\">\r\n <ng-content select=\"[filter-more]\" />\r\n </div>\r\n </div>\r\n <div class=\"fp__morebar\">\r\n <button\r\n type=\"button\"\r\n class=\"fp__morebtn\"\r\n (click)=\"showMore.set(!showMore())\"\r\n [attr.aria-expanded]=\"showMore()\"\r\n >\r\n <svg class=\"fp__moreico\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <path [attr.d]=\"showMore() ? 'M3.5 8h9' : 'M8 3.5v9M3.5 8h9'\" />\r\n </svg>\r\n {{ (showMore() ? 'ui.action.fewerOptions' : 'ui.action.addMoreOptions') | translate }}\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (showSearchButton()) {\r\n <div class=\"fp__footer\">\r\n <button type=\"button\" fly-button variant=\"primary\" (click)=\"searched.emit()\">\r\n {{ 'ui.action.search' | translate }}\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n }\r\n \r\n", styles: ["@charset \"UTF-8\";:host{display:block;container-type:inline-size}.fp{margin-block-end:var(--sp-4);border-radius:var(--r-lg);overflow:hidden;animation:fly-fp-in var(--t-overlay);border:1px solid var(--glass2-border);background-image:var(--glass2-bg);box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset)}@media(prefers-reduced-transparency:reduce){.fp{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.fp:before,.fp:after{display:none}}@media(prefers-contrast:more){.fp{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.fp:after{animation:none}}:host(.fp--anchored){--fp-shift-x: 0px;--fp-max-w: calc(100vw - 2 * var(--sp-4));position:absolute;inset-block-start:calc(100% + var(--sp-2));inset-inline-end:0;z-index:var(--z-overlay);width:min(600px,var(--fp-max-w));transform:translate(var(--fp-shift-x))}:host(.fp--floating){--fp-max-w: calc(100vw - 2 * var(--sp-4));--fp-max-h: calc(100vh - 2 * var(--sp-4));--fp-x: 0px;--fp-y: 0px;position:fixed;inset-block-start:var(--fp-y);inset-inline-start:auto;left:var(--fp-x);z-index:var(--z-overlay);width:min(600px,var(--fp-max-w));max-height:var(--fp-max-h)}:host(.fp--floating) .fp{margin-block-end:0;max-height:var(--fp-max-h);display:flex;flex-direction:column}:host(.fp--floating) .fp__body{flex:1 1 auto;min-height:0;overflow-y:auto}:host(.fp--anchored) .fp{margin-block-end:0}:host(.fp--anchored) .fp__body{max-height:min(320px,44vh);overflow-y:auto}.fp__head{display:flex;align-items:center;justify-content:space-between;padding:var(--sp-3) var(--sp-4);border-block-end:.5px solid var(--w1)}.fp__title{font-size:var(--text-xs);font-weight:var(--fw-medium);letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3)}.fp__actions{display:flex;align-items:center;gap:var(--sp-1)}.fp__clear{border:0;background:transparent;font-size:12px;color:var(--ink-3);padding:var(--sp-1) var(--sp-2);border-radius:var(--r-sm);cursor:pointer;font-family:inherit;transition:background var(--t-state),color var(--t-state)}.fp__clear:hover{color:var(--ink);background:var(--w08)}.fp__clear:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fp__close{width:24px;height:24px;border:0;background:transparent;color:var(--ink-3);cursor:pointer;border-radius:var(--r-sm);font-size:var(--text-xl);line-height:1;display:grid;place-items:center}.fp__close:hover{color:var(--ink);background:var(--w08)}.fp__close:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fp ::ng-deep [filter-status]{display:flex;flex-direction:column;gap:var(--sp-2);padding:14px var(--sp-4);border-block-end:.5px solid var(--w08)}.fp__grid{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:14px var(--sp-4);padding:var(--sp-4)}@container (width <= 1180px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(4,minmax(0,1fr))}}@media(width<=1180px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(4,minmax(0,1fr))}}@container (width <= 820px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(width<=820px){.fp[data-layout=grid] .fp__grid{grid-template-columns:repeat(2,minmax(0,1fr))}}.fp[data-layout=rows] .fp__grid{grid-template-columns:1fr;gap:var(--sp-2)}.fp[data-layout=rows] ::ng-deep .fld{display:grid;grid-template-columns:minmax(110px,168px) 1fr;align-items:center;gap:var(--sp-1) var(--sp-4)}.fp[data-layout=rows] ::ng-deep .fld__l{margin:0;color:var(--ink-2)}.fp[data-layout=rows] ::ng-deep .fld__hint,.fp[data-layout=rows] ::ng-deep .fld__err{grid-column:2}@container (width <= 640px){.fp[data-layout=rows] ::ng-deep .fld{grid-template-columns:1fr}.fp[data-layout=rows] ::ng-deep .fld__hint,.fp[data-layout=rows] ::ng-deep .fld__err{grid-column:1}}@media(width<=640px){.fp[data-layout=rows] ::ng-deep .fld{grid-template-columns:1fr}.fp[data-layout=rows] ::ng-deep .fld__hint,.fp[data-layout=rows] ::ng-deep .fld__err{grid-column:1}}.fp__more{border-block-start:.5px solid var(--w08)}.fp__more .fp__grid{padding-block-start:var(--sp-3)}.fp__morebar{padding:0 var(--sp-4) var(--sp-3)}.fp__morebtn{display:inline-flex;align-items:center;gap:var(--sp-1);border:0;background:transparent;padding:var(--sp-1) var(--sp-2);border-radius:var(--r-sm);font-family:inherit;font-size:var(--text-sm);font-weight:var(--fw-medium);color:var(--accent);cursor:pointer;transition:background var(--t-state)}.fp__morebtn:hover{background:var(--w08)}.fp__morebtn:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fp__moreico{width:14px;height:14px;flex-shrink:0}.fp__footer{display:flex;justify-content:flex-end;padding:var(--sp-3) var(--sp-4);border-block-start:.5px solid var(--w08)}.fp__footer button{background:var(--grad);border:0;color:var(--ink-inverse);box-shadow:var(--shadow-raised)}.fp__footer button:hover{background:var(--grad);border:0;filter:brightness(1.05);box-shadow:var(--shadow-accent)}@keyframes fly-fp-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}\n"], dependencies: [{ kind: "component", type: FlyButtonComponent, selector: "button[fly-button], a[fly-button]", inputs: ["variant", "size", "loading"] }, { kind: "directive", type: FlyClickOutsideDirective, selector: "[flyClickOutside]", inputs: ["flyClickOutsideEnabled", "flyClickOutsideIgnore"], outputs: ["flyClickOutside"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
22245
22925
|
}
|
|
22246
22926
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyFilterPanelComponent, decorators: [{
|
|
22247
22927
|
type: Component,
|
|
22248
|
-
args: [{ selector: 'fly-filter-panel', standalone: true, imports: [TranslatePipe, FlyButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
22249
|
-
|
|
22928
|
+
args: [{ selector: 'fly-filter-panel', standalone: true, imports: [TranslatePipe, FlyButtonComponent, FlyClickOutsideDirective], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
22929
|
+
'[class.fp--anchored]': 'anchored() && !floating()',
|
|
22930
|
+
'[class.fp--floating]': 'floating()',
|
|
22931
|
+
}, 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"] }]
|
|
22932
|
+
}], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: true }] }], titleKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleKey", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], hasMore: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasMore", required: false }] }], showSearchButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSearchButton", required: false }] }], anchored: [{ type: i0.Input, args: [{ isSignal: true, alias: "anchored", required: false }] }], anchorEl: [{ type: i0.Input, args: [{ isSignal: true, alias: "anchorEl", required: false }] }], cleared: [{ type: i0.Output, args: ["cleared"] }], closed: [{ type: i0.Output, args: ["closed"] }], searched: [{ type: i0.Output, args: ["searched"] }] } });
|
|
22250
22933
|
|
|
22251
22934
|
/**
|
|
22252
22935
|
* Per-column cell override for {@link FlyDataTableComponent}. Declared on an
|
|
@@ -22602,11 +23285,11 @@ class PageHeaderComponent {
|
|
|
22602
23285
|
return '#';
|
|
22603
23286
|
}
|
|
22604
23287
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: PageHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
22605
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: PageHeaderComponent, isStandalone: true, selector: "fly-page-header", inputs: { variant: "variant", title: "title", titleKey: "titleKey", subtitle: "subtitle", subtitleKey: "subtitleKey", titleAr: "titleAr", eyebrow: "eyebrow", eyebrowKey: "eyebrowKey", metricValue: "metricValue", metricLabel: "metricLabel", metricLabelKey: "metricLabelKey", breadcrumbs: "breadcrumbs" }, outputs: { crumbClick: "crumbClick" }, ngImport: i0, template: "<header class=\"ph\" [ngClass]=\"'ph--' + variant\">\r\n <!-- \u2500\u2500\u2500 Breadcrumbs \u2500\u2500\u2500 -->\r\n @if (breadcrumbs?.length) {\r\n <nav class=\"crumbs\" [attr.aria-label]=\"'ui.nav.breadcrumbs' | translate\">\r\n @for (bc of breadcrumbs; track $index; let i = $index, last = $last) {\r\n @if (i > 0) {\r\n <span class=\"crumb__sep\" aria-hidden=\"true\">/</span>\r\n }\r\n @if (last || bc.ariaCurrent) {\r\n <span class=\"crumb crumb--active\" aria-current=\"page\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </span>\r\n } @else if (bc.onClick) {\r\n <button type=\"button\" class=\"crumb crumb--link\" (click)=\"onCrumb(bc)\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </button>\r\n } @else if (bc.link) {\r\n <a class=\"crumb crumb--link\" (click)=\"onCrumb(bc); $event.preventDefault()\" [attr.href]=\"hrefFor(bc)\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </a>\r\n } @else if (bc.href) {\r\n <a class=\"crumb crumb--link\" [attr.href]=\"bc.href\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </a>\r\n } @else {\r\n <span class=\"crumb\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </span>\r\n }\r\n }\r\n </nav>\r\n }\r\n\r\n <!-- \u2500\u2500\u2500 Title row \u2500\u2500\u2500 -->\r\n <div class=\"ph__row\">\r\n <div class=\"ph__l\">\r\n @if (eyebrow || eyebrowKey) {\r\n <div class=\"ph__eyebrow\">\r\n @if (eyebrow) { <span class=\"mono\">{{ eyebrow }}</span> }\r\n @if (eyebrowKey) { <span>{{ eyebrowKey | translate }}</span> }\r\n </div>\r\n }\r\n <h1 class=\"ph__title\" [ngClass]=\"{ 'ph__title--detail': variant === 'detail' }\">\r\n {{ titleKey ? (titleKey | translate) : title }}\r\n </h1>\r\n @if (subtitle || subtitleKey) {\r\n <p class=\"ph__sub\">{{ subtitleKey ? (subtitleKey | translate) : subtitle }}</p>\r\n }\r\n @if (titleAr) {\r\n <p class=\"ph__title-ar\">{{ titleAr }}</p>\r\n }\r\n </div>\r\n\r\n <div class=\"ph__r\">\r\n <!-- Right-side metric (listing) -->\r\n @if (metricValue || metricLabel || metricLabelKey) {\r\n <div class=\"ph__metric\">\r\n @if (metricValue) { <div class=\"ph__metric-v mono\">{{ metricValue }}</div> }\r\n <div class=\"ph__metric-l\">{{ metricLabelKey ? (metricLabelKey | translate) : metricLabel }}</div>\r\n </div>\r\n }\r\n\r\n <!-- Status chip (detail) stacked over the action button row. Listing/circles/module\r\n project no status, so this collapses to just the right-aligned action row.\r\n NOTE: a single [page-header-actions] slot \u2014 duplicating it across @if branches\r\n silently drops projected content in the non-first branch (Angular binds projection\r\n to the first declared matching <ng-content>). -->\r\n <div class=\"ph__hd-actions\">\r\n <ng-content select=\"[page-header-status]\"></ng-content>\r\n <div class=\"ph__hd-btns\">\r\n <ng-content select=\"[page-header-actions]\"></ng-content>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Default slot: anything the consumer wants directly below the title row\r\n (e.g. a detail-page stat bar). -->\r\n <ng-content></ng-content>\r\n</header>\r\n", styles: ["@charset \"UTF-8\";:host{display:block;margin-block-end:var(--sp-6);container-type:inline-size}.ph{display:flex;flex-direction:column;gap:0;color:var(--ink);font-family:var(--font-sans, var(--font-family, system-ui, sans-serif))}.crumbs{display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-size:12px;margin-bottom:14px;color:var(--ink-3)}.crumb{font-size:12px;line-height:1.4;color:var(--ink-3);padding:2px 0;background:none;border:0;font-family:inherit;letter-spacing:-.005em;text-decoration:none}.crumb--link{cursor:pointer;padding-inline:4px;margin-inline:-4px;border-radius:6px;transition:color var(--t-state),background var(--t-state)}.crumb--link:hover{color:var(--ink);background:var(--w08)}.crumb--active{color:var(--ink);font-weight:var(--fw-medium);max-width:480px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.crumb__sep{color:var(--ink-4);font-size:11px;-webkit-user-select:none;user-select:none}.ph__row{display:flex;align-items:flex-end;justify-content:space-between;gap:12px 32px;flex-wrap:wrap}.ph--detail .ph__row{align-items:flex-start}.ph__l{flex:1}.ph__r{display:flex;align-items:flex-end;gap:16px;flex-shrink:0}.ph--detail .ph__r{align-items:flex-end;flex-direction:column}.ph__eyebrow{display:inline-flex;align-items:center;gap:6px;font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-4);font-weight:var(--fw-medium);margin-bottom:4px}.ph__eyebrow .mono{font-size:10.5px;color:var(--ink-3);letter-spacing:.04em;font-family:var(--font-mono, ui-monospace, monospace)}.ph__title{margin:0 0 6px;font-size:32px;font-weight:var(--fw-semibold);letter-spacing:-.02em;line-height:1.1;color:var(--ink)}.ph__title--detail{font-size:28px;letter-spacing:-.02em;line-height:1.2;margin:0}.ph__sub{margin:0;font-size:13.5px;color:var(--ink-3)}.ph__title-ar{margin:6px 0 0;font-size:17px;color:var(--ink-3);
|
|
23288
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: PageHeaderComponent, isStandalone: true, selector: "fly-page-header", inputs: { variant: "variant", title: "title", titleKey: "titleKey", subtitle: "subtitle", subtitleKey: "subtitleKey", titleAr: "titleAr", eyebrow: "eyebrow", eyebrowKey: "eyebrowKey", metricValue: "metricValue", metricLabel: "metricLabel", metricLabelKey: "metricLabelKey", breadcrumbs: "breadcrumbs" }, outputs: { crumbClick: "crumbClick" }, ngImport: i0, template: "<header class=\"ph\" [ngClass]=\"'ph--' + variant\">\r\n <!-- \u2500\u2500\u2500 Breadcrumbs \u2500\u2500\u2500 -->\r\n @if (breadcrumbs?.length) {\r\n <nav class=\"crumbs\" [attr.aria-label]=\"'ui.nav.breadcrumbs' | translate\">\r\n @for (bc of breadcrumbs; track $index; let i = $index, last = $last) {\r\n @if (i > 0) {\r\n <span class=\"crumb__sep\" aria-hidden=\"true\">/</span>\r\n }\r\n @if (last || bc.ariaCurrent) {\r\n <span class=\"crumb crumb--active\" aria-current=\"page\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </span>\r\n } @else if (bc.onClick) {\r\n <button type=\"button\" class=\"crumb crumb--link\" (click)=\"onCrumb(bc)\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </button>\r\n } @else if (bc.link) {\r\n <a class=\"crumb crumb--link\" (click)=\"onCrumb(bc); $event.preventDefault()\" [attr.href]=\"hrefFor(bc)\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </a>\r\n } @else if (bc.href) {\r\n <a class=\"crumb crumb--link\" [attr.href]=\"bc.href\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </a>\r\n } @else {\r\n <span class=\"crumb\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </span>\r\n }\r\n }\r\n </nav>\r\n }\r\n\r\n <!-- \u2500\u2500\u2500 Title row \u2500\u2500\u2500 -->\r\n <div class=\"ph__row\">\r\n <div class=\"ph__l\">\r\n @if (eyebrow || eyebrowKey) {\r\n <div class=\"ph__eyebrow\">\r\n @if (eyebrow) { <span class=\"mono\">{{ eyebrow }}</span> }\r\n @if (eyebrowKey) { <span>{{ eyebrowKey | translate }}</span> }\r\n </div>\r\n }\r\n <h1 class=\"ph__title\" [ngClass]=\"{ 'ph__title--detail': variant === 'detail' }\">\r\n {{ titleKey ? (titleKey | translate) : title }}\r\n </h1>\r\n @if (subtitle || subtitleKey) {\r\n <p class=\"ph__sub\">{{ subtitleKey ? (subtitleKey | translate) : subtitle }}</p>\r\n }\r\n @if (titleAr) {\r\n <p class=\"ph__title-ar\">{{ titleAr }}</p>\r\n }\r\n </div>\r\n\r\n <div class=\"ph__r\">\r\n <!-- Right-side metric (listing) -->\r\n @if (metricValue || metricLabel || metricLabelKey) {\r\n <div class=\"ph__metric\">\r\n @if (metricValue) { <div class=\"ph__metric-v mono\">{{ metricValue }}</div> }\r\n <div class=\"ph__metric-l\">{{ metricLabelKey ? (metricLabelKey | translate) : metricLabel }}</div>\r\n </div>\r\n }\r\n\r\n <!-- Status chip (detail) stacked over the action button row. Listing/circles/module\r\n project no status, so this collapses to just the right-aligned action row.\r\n NOTE: a single [page-header-actions] slot \u2014 duplicating it across @if branches\r\n silently drops projected content in the non-first branch (Angular binds projection\r\n to the first declared matching <ng-content>). -->\r\n <div class=\"ph__hd-actions\">\r\n <ng-content select=\"[page-header-status]\"></ng-content>\r\n <div class=\"ph__hd-btns\">\r\n <ng-content select=\"[page-header-actions]\"></ng-content>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Default slot: anything the consumer wants directly below the title row\r\n (e.g. a detail-page stat bar). -->\r\n <ng-content></ng-content>\r\n</header>\r\n", styles: ["@charset \"UTF-8\";:host{display:block;margin-block-end:var(--sp-6);container-type:inline-size}.ph{display:flex;flex-direction:column;gap:0;color:var(--ink);font-family:var(--font-sans, var(--font-family, system-ui, sans-serif))}.crumbs{display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-size:12px;margin-bottom:14px;color:var(--ink-3)}.crumb{font-size:12px;line-height:1.4;color:var(--ink-3);padding:2px 0;background:none;border:0;font-family:inherit;letter-spacing:-.005em;text-decoration:none}.crumb--link{cursor:pointer;padding-inline:4px;margin-inline:-4px;border-radius:6px;transition:color var(--t-state),background var(--t-state)}.crumb--link:hover{color:var(--ink);background:var(--w08)}.crumb--active{color:var(--ink);font-weight:var(--fw-medium);max-width:480px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.crumb__sep{color:var(--ink-4);font-size:11px;-webkit-user-select:none;user-select:none}.ph__row{display:flex;align-items:flex-end;justify-content:space-between;gap:12px 32px;flex-wrap:wrap}.ph--detail .ph__row{align-items:flex-start}.ph__l{flex:1}.ph__r{display:flex;align-items:flex-end;gap:16px;flex-shrink:0}.ph--detail .ph__r{align-items:flex-end;flex-direction:column}.ph__eyebrow{display:inline-flex;align-items:center;gap:6px;font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-4);font-weight:var(--fw-medium);margin-bottom:4px}.ph__eyebrow .mono{font-size:10.5px;color:var(--ink-3);letter-spacing:.04em;font-family:var(--font-mono, ui-monospace, monospace)}.ph__title{margin:0 0 6px;font-size:32px;font-weight:var(--fw-semibold);letter-spacing:-.02em;line-height:1.1;color:var(--ink)}.ph__title--detail{font-size:28px;letter-spacing:-.02em;line-height:1.2;margin:0}.ph__sub{margin:0;font-size:13.5px;color:var(--ink-3)}.ph__title-ar{margin:6px 0 0;font-size:17px;color:var(--ink-3);letter-spacing:0;line-height:1.5;font-weight:var(--fw-medium)}.ph__metric{text-align:end}.ph__metric-v{font-family:var(--font-mono, ui-monospace, monospace);font-variant-numeric:tabular-nums;font-size:22px;font-weight:var(--fw-medium);letter-spacing:-.02em;color:var(--ink)}.ph__metric-l{font-size:11px;color:var(--ink-3);text-transform:uppercase;letter-spacing:.06em}.ph__hd-actions{display:flex;flex-direction:column;align-items:flex-end;gap:10px}.ph__hd-btns{display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end}.ph--listing,.ph--detail{margin-bottom:0}@container (width <= 640px){.ph__row{flex-direction:column;align-items:flex-start;gap:16px}.ph__r{align-items:flex-start;flex-flow:row wrap}.ph--detail .ph__r{flex-direction:row-reverse}.ph__title{font-size:26px}.ph__title--detail{font-size:24px}}@media(width<=640px){.ph__row{flex-direction:column;align-items:flex-start;gap:16px}.ph__r{align-items:flex-start;flex-flow:row wrap}.ph--detail .ph__r{flex-direction:row-reverse}.ph__title{font-size:26px}.ph__title--detail{font-size:24px}}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
22606
23289
|
}
|
|
22607
23290
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: PageHeaderComponent, decorators: [{
|
|
22608
23291
|
type: Component,
|
|
22609
|
-
args: [{ selector: 'fly-page-header', standalone: true, imports: [NgClass, TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<header class=\"ph\" [ngClass]=\"'ph--' + variant\">\r\n <!-- \u2500\u2500\u2500 Breadcrumbs \u2500\u2500\u2500 -->\r\n @if (breadcrumbs?.length) {\r\n <nav class=\"crumbs\" [attr.aria-label]=\"'ui.nav.breadcrumbs' | translate\">\r\n @for (bc of breadcrumbs; track $index; let i = $index, last = $last) {\r\n @if (i > 0) {\r\n <span class=\"crumb__sep\" aria-hidden=\"true\">/</span>\r\n }\r\n @if (last || bc.ariaCurrent) {\r\n <span class=\"crumb crumb--active\" aria-current=\"page\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </span>\r\n } @else if (bc.onClick) {\r\n <button type=\"button\" class=\"crumb crumb--link\" (click)=\"onCrumb(bc)\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </button>\r\n } @else if (bc.link) {\r\n <a class=\"crumb crumb--link\" (click)=\"onCrumb(bc); $event.preventDefault()\" [attr.href]=\"hrefFor(bc)\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </a>\r\n } @else if (bc.href) {\r\n <a class=\"crumb crumb--link\" [attr.href]=\"bc.href\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </a>\r\n } @else {\r\n <span class=\"crumb\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </span>\r\n }\r\n }\r\n </nav>\r\n }\r\n\r\n <!-- \u2500\u2500\u2500 Title row \u2500\u2500\u2500 -->\r\n <div class=\"ph__row\">\r\n <div class=\"ph__l\">\r\n @if (eyebrow || eyebrowKey) {\r\n <div class=\"ph__eyebrow\">\r\n @if (eyebrow) { <span class=\"mono\">{{ eyebrow }}</span> }\r\n @if (eyebrowKey) { <span>{{ eyebrowKey | translate }}</span> }\r\n </div>\r\n }\r\n <h1 class=\"ph__title\" [ngClass]=\"{ 'ph__title--detail': variant === 'detail' }\">\r\n {{ titleKey ? (titleKey | translate) : title }}\r\n </h1>\r\n @if (subtitle || subtitleKey) {\r\n <p class=\"ph__sub\">{{ subtitleKey ? (subtitleKey | translate) : subtitle }}</p>\r\n }\r\n @if (titleAr) {\r\n <p class=\"ph__title-ar\">{{ titleAr }}</p>\r\n }\r\n </div>\r\n\r\n <div class=\"ph__r\">\r\n <!-- Right-side metric (listing) -->\r\n @if (metricValue || metricLabel || metricLabelKey) {\r\n <div class=\"ph__metric\">\r\n @if (metricValue) { <div class=\"ph__metric-v mono\">{{ metricValue }}</div> }\r\n <div class=\"ph__metric-l\">{{ metricLabelKey ? (metricLabelKey | translate) : metricLabel }}</div>\r\n </div>\r\n }\r\n\r\n <!-- Status chip (detail) stacked over the action button row. Listing/circles/module\r\n project no status, so this collapses to just the right-aligned action row.\r\n NOTE: a single [page-header-actions] slot \u2014 duplicating it across @if branches\r\n silently drops projected content in the non-first branch (Angular binds projection\r\n to the first declared matching <ng-content>). -->\r\n <div class=\"ph__hd-actions\">\r\n <ng-content select=\"[page-header-status]\"></ng-content>\r\n <div class=\"ph__hd-btns\">\r\n <ng-content select=\"[page-header-actions]\"></ng-content>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Default slot: anything the consumer wants directly below the title row\r\n (e.g. a detail-page stat bar). -->\r\n <ng-content></ng-content>\r\n</header>\r\n", styles: ["@charset \"UTF-8\";:host{display:block;margin-block-end:var(--sp-6);container-type:inline-size}.ph{display:flex;flex-direction:column;gap:0;color:var(--ink);font-family:var(--font-sans, var(--font-family, system-ui, sans-serif))}.crumbs{display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-size:12px;margin-bottom:14px;color:var(--ink-3)}.crumb{font-size:12px;line-height:1.4;color:var(--ink-3);padding:2px 0;background:none;border:0;font-family:inherit;letter-spacing:-.005em;text-decoration:none}.crumb--link{cursor:pointer;padding-inline:4px;margin-inline:-4px;border-radius:6px;transition:color var(--t-state),background var(--t-state)}.crumb--link:hover{color:var(--ink);background:var(--w08)}.crumb--active{color:var(--ink);font-weight:var(--fw-medium);max-width:480px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.crumb__sep{color:var(--ink-4);font-size:11px;-webkit-user-select:none;user-select:none}.ph__row{display:flex;align-items:flex-end;justify-content:space-between;gap:12px 32px;flex-wrap:wrap}.ph--detail .ph__row{align-items:flex-start}.ph__l{flex:1}.ph__r{display:flex;align-items:flex-end;gap:16px;flex-shrink:0}.ph--detail .ph__r{align-items:flex-end;flex-direction:column}.ph__eyebrow{display:inline-flex;align-items:center;gap:6px;font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-4);font-weight:var(--fw-medium);margin-bottom:4px}.ph__eyebrow .mono{font-size:10.5px;color:var(--ink-3);letter-spacing:.04em;font-family:var(--font-mono, ui-monospace, monospace)}.ph__title{margin:0 0 6px;font-size:32px;font-weight:var(--fw-semibold);letter-spacing:-.02em;line-height:1.1;color:var(--ink)}.ph__title--detail{font-size:28px;letter-spacing:-.02em;line-height:1.2;margin:0}.ph__sub{margin:0;font-size:13.5px;color:var(--ink-3)}.ph__title-ar{margin:6px 0 0;font-size:17px;color:var(--ink-3);
|
|
23292
|
+
args: [{ selector: 'fly-page-header', standalone: true, imports: [NgClass, TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<header class=\"ph\" [ngClass]=\"'ph--' + variant\">\r\n <!-- \u2500\u2500\u2500 Breadcrumbs \u2500\u2500\u2500 -->\r\n @if (breadcrumbs?.length) {\r\n <nav class=\"crumbs\" [attr.aria-label]=\"'ui.nav.breadcrumbs' | translate\">\r\n @for (bc of breadcrumbs; track $index; let i = $index, last = $last) {\r\n @if (i > 0) {\r\n <span class=\"crumb__sep\" aria-hidden=\"true\">/</span>\r\n }\r\n @if (last || bc.ariaCurrent) {\r\n <span class=\"crumb crumb--active\" aria-current=\"page\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </span>\r\n } @else if (bc.onClick) {\r\n <button type=\"button\" class=\"crumb crumb--link\" (click)=\"onCrumb(bc)\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </button>\r\n } @else if (bc.link) {\r\n <a class=\"crumb crumb--link\" (click)=\"onCrumb(bc); $event.preventDefault()\" [attr.href]=\"hrefFor(bc)\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </a>\r\n } @else if (bc.href) {\r\n <a class=\"crumb crumb--link\" [attr.href]=\"bc.href\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </a>\r\n } @else {\r\n <span class=\"crumb\">\r\n {{ bc.labelKey ? (bc.labelKey | translate) : bc.label }}\r\n </span>\r\n }\r\n }\r\n </nav>\r\n }\r\n\r\n <!-- \u2500\u2500\u2500 Title row \u2500\u2500\u2500 -->\r\n <div class=\"ph__row\">\r\n <div class=\"ph__l\">\r\n @if (eyebrow || eyebrowKey) {\r\n <div class=\"ph__eyebrow\">\r\n @if (eyebrow) { <span class=\"mono\">{{ eyebrow }}</span> }\r\n @if (eyebrowKey) { <span>{{ eyebrowKey | translate }}</span> }\r\n </div>\r\n }\r\n <h1 class=\"ph__title\" [ngClass]=\"{ 'ph__title--detail': variant === 'detail' }\">\r\n {{ titleKey ? (titleKey | translate) : title }}\r\n </h1>\r\n @if (subtitle || subtitleKey) {\r\n <p class=\"ph__sub\">{{ subtitleKey ? (subtitleKey | translate) : subtitle }}</p>\r\n }\r\n @if (titleAr) {\r\n <p class=\"ph__title-ar\">{{ titleAr }}</p>\r\n }\r\n </div>\r\n\r\n <div class=\"ph__r\">\r\n <!-- Right-side metric (listing) -->\r\n @if (metricValue || metricLabel || metricLabelKey) {\r\n <div class=\"ph__metric\">\r\n @if (metricValue) { <div class=\"ph__metric-v mono\">{{ metricValue }}</div> }\r\n <div class=\"ph__metric-l\">{{ metricLabelKey ? (metricLabelKey | translate) : metricLabel }}</div>\r\n </div>\r\n }\r\n\r\n <!-- Status chip (detail) stacked over the action button row. Listing/circles/module\r\n project no status, so this collapses to just the right-aligned action row.\r\n NOTE: a single [page-header-actions] slot \u2014 duplicating it across @if branches\r\n silently drops projected content in the non-first branch (Angular binds projection\r\n to the first declared matching <ng-content>). -->\r\n <div class=\"ph__hd-actions\">\r\n <ng-content select=\"[page-header-status]\"></ng-content>\r\n <div class=\"ph__hd-btns\">\r\n <ng-content select=\"[page-header-actions]\"></ng-content>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Default slot: anything the consumer wants directly below the title row\r\n (e.g. a detail-page stat bar). -->\r\n <ng-content></ng-content>\r\n</header>\r\n", styles: ["@charset \"UTF-8\";:host{display:block;margin-block-end:var(--sp-6);container-type:inline-size}.ph{display:flex;flex-direction:column;gap:0;color:var(--ink);font-family:var(--font-sans, var(--font-family, system-ui, sans-serif))}.crumbs{display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-size:12px;margin-bottom:14px;color:var(--ink-3)}.crumb{font-size:12px;line-height:1.4;color:var(--ink-3);padding:2px 0;background:none;border:0;font-family:inherit;letter-spacing:-.005em;text-decoration:none}.crumb--link{cursor:pointer;padding-inline:4px;margin-inline:-4px;border-radius:6px;transition:color var(--t-state),background var(--t-state)}.crumb--link:hover{color:var(--ink);background:var(--w08)}.crumb--active{color:var(--ink);font-weight:var(--fw-medium);max-width:480px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.crumb__sep{color:var(--ink-4);font-size:11px;-webkit-user-select:none;user-select:none}.ph__row{display:flex;align-items:flex-end;justify-content:space-between;gap:12px 32px;flex-wrap:wrap}.ph--detail .ph__row{align-items:flex-start}.ph__l{flex:1}.ph__r{display:flex;align-items:flex-end;gap:16px;flex-shrink:0}.ph--detail .ph__r{align-items:flex-end;flex-direction:column}.ph__eyebrow{display:inline-flex;align-items:center;gap:6px;font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-4);font-weight:var(--fw-medium);margin-bottom:4px}.ph__eyebrow .mono{font-size:10.5px;color:var(--ink-3);letter-spacing:.04em;font-family:var(--font-mono, ui-monospace, monospace)}.ph__title{margin:0 0 6px;font-size:32px;font-weight:var(--fw-semibold);letter-spacing:-.02em;line-height:1.1;color:var(--ink)}.ph__title--detail{font-size:28px;letter-spacing:-.02em;line-height:1.2;margin:0}.ph__sub{margin:0;font-size:13.5px;color:var(--ink-3)}.ph__title-ar{margin:6px 0 0;font-size:17px;color:var(--ink-3);letter-spacing:0;line-height:1.5;font-weight:var(--fw-medium)}.ph__metric{text-align:end}.ph__metric-v{font-family:var(--font-mono, ui-monospace, monospace);font-variant-numeric:tabular-nums;font-size:22px;font-weight:var(--fw-medium);letter-spacing:-.02em;color:var(--ink)}.ph__metric-l{font-size:11px;color:var(--ink-3);text-transform:uppercase;letter-spacing:.06em}.ph__hd-actions{display:flex;flex-direction:column;align-items:flex-end;gap:10px}.ph__hd-btns{display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end}.ph--listing,.ph--detail{margin-bottom:0}@container (width <= 640px){.ph__row{flex-direction:column;align-items:flex-start;gap:16px}.ph__r{align-items:flex-start;flex-flow:row wrap}.ph--detail .ph__r{flex-direction:row-reverse}.ph__title{font-size:26px}.ph__title--detail{font-size:24px}}@media(width<=640px){.ph__row{flex-direction:column;align-items:flex-start;gap:16px}.ph__r{align-items:flex-start;flex-flow:row wrap}.ph--detail .ph__r{flex-direction:row-reverse}.ph__title{font-size:26px}.ph__title--detail{font-size:24px}}\n"] }]
|
|
22610
23293
|
}], propDecorators: { variant: [{
|
|
22611
23294
|
type: Input
|
|
22612
23295
|
}], title: [{
|
|
@@ -23292,14 +23975,14 @@ class FlyAppTopbarComponent {
|
|
|
23292
23975
|
this.rows()[index]?.nativeElement.focus();
|
|
23293
23976
|
}
|
|
23294
23977
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyAppTopbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
23295
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyAppTopbarComponent, isStandalone: true, selector: "fly-app-topbar", inputs: { brandLabelKey: { classPropertyName: "brandLabelKey", publicName: "brandLabelKey", isSignal: true, isRequired: false, transformFunction: null }, sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: false, transformFunction: null }, activeKey: { classPropertyName: "activeKey", publicName: "activeKey", isSignal: true, isRequired: false, transformFunction: null }, footerLabel: { classPropertyName: "footerLabel", publicName: "footerLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { moduleSelected: "moduleSelected", brandSelected: "brandSelected" }, host: { listeners: { "document:keydown.escape": "onEscape()" }, properties: { "class.fly-topbar--docked": "docked()" } }, queries: [{ propertyName: "icons", predicate: FlyModuleIconDirective, isSignal: true }], viewQueries: [{ propertyName: "rows", predicate: ["row"], descendants: true, isSignal: true }, { propertyName: "trigger", predicate: ["trigger"], descendants: true, isSignal: true }], ngImport: i0, template: "<header class=\"fly-topbar\">\r\n <div class=\"fly-topbar__lead\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-topbar__brand\"\r\n (click)=\"brandSelected.emit()\"\r\n [attr.aria-label]=\"brandAriaLabel()\"\r\n >\r\n <span class=\"fly-topbar__brand-mark\"><ng-content select=\"[app-topbar-brand]\" /></span>\r\n @if (brandLabelKey(); as key) {\r\n <span class=\"fly-topbar__brand-name\">{{ key | translate }}</span>\r\n }\r\n </button>\r\n\r\n @if (activeModule()) {\r\n <span class=\"fly-topbar__sep\" aria-hidden=\"true\">/</span>\r\n }\r\n\r\n <div class=\"fly-topbar__anchor\" (flyClickOutside)=\"close()\" [flyClickOutsideEnabled]=\"open()\">\r\n <button\r\n #trigger\r\n type=\"button\"\r\n class=\"fly-topbar__module\"\r\n [class.fly-topbar__module--on]=\"open()\"\r\n (click)=\"toggle()\"\r\n (keydown)=\"onTriggerKeydown($event)\"\r\n aria-haspopup=\"menu\"\r\n [attr.aria-expanded]=\"open()\"\r\n [attr.aria-label]=\"triggerAriaLabel()\"\r\n >\r\n @if (activeModule(); as active) {\r\n <span class=\"fly-topbar__module-icon\">\r\n @if (iconFor(active.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (active.iconClass) {\r\n <i [class]=\"active.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n <span class=\"fly-topbar__module-label\">{{ active.labelKey | translate }}</span>\r\n } @else {\r\n <span class=\"fly-topbar__module-label fly-topbar__module-label--empty\">\r\n {{ 'ui.nav.selectModule' | translate }}\r\n </span>\r\n }\r\n <svg class=\"fly-topbar__chevron\" width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\">\r\n <polyline points=\"6 9 12 15 18 9\" />\r\n </svg>\r\n </button>\r\n\r\n @if (open()) {\r\n <div class=\"fly-topbar__pop\" role=\"menu\" [attr.aria-label]=\"switchAriaLabel()\">\r\n <div class=\"fly-topbar__cols\">\r\n @for (section of sections(); track $index) {\r\n <div class=\"fly-topbar__col\">\r\n @if (section.titleKey; as titleKey) {\r\n <div class=\"fly-topbar__col-title\">{{ titleKey | translate }}</div>\r\n }\r\n @for (mod of section.modules; track mod.key) {\r\n <button\r\n #row\r\n type=\"button\"\r\n class=\"fly-topbar__item\"\r\n [class.fly-topbar__item--on]=\"mod.key === activeKey()\"\r\n role=\"menuitem\"\r\n [disabled]=\"mod.disabled ?? false\"\r\n [attr.tabindex]=\"indexOf(mod) === focusIndex() ? 0 : -1\"\r\n [attr.aria-current]=\"mod.key === activeKey() ? 'page' : null\"\r\n (click)=\"select(mod)\"\r\n (keydown)=\"onRowKeydown($event)\"\r\n >\r\n <span class=\"fly-topbar__item-icon\">\r\n @if (iconFor(mod.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (mod.iconClass) {\r\n <i [class]=\"mod.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n <span class=\"fly-topbar__item-main\">\r\n <span class=\"fly-topbar__item-label\">{{ mod.labelKey | translate }}</span>\r\n @if (mod.descKey; as descKey) {\r\n <span class=\"fly-topbar__item-desc\">{{ descKey | translate }}</span>\r\n }\r\n </span>\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n @if (footerLabel()) {\r\n <div class=\"fly-topbar__foot\">{{ footerLabel() }}</div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"fly-topbar__actions\"><ng-content select=\"[app-topbar-actions]\" /></div>\r\n</header>\r\n", styles: [":host{display:block;position:relative;z-index:var(--z-sticky)}.fly-topbar{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-3);padding:var(--sp-2) var(--sp-3);border-block-end:1px solid var(--w1);background:var(--bg-3)}.fly-topbar__lead{display:flex;align-items:center;gap:var(--sp-2);min-inline-size:0}.fly-topbar__brand,.fly-topbar__module{display:inline-flex;align-items:center;gap:var(--sp-2);appearance:none;border:0;background:transparent;cursor:pointer;font:inherit;color:var(--ink);padding:var(--sp-1) var(--sp-2);border-radius:var(--r-md);transition:background var(--t-state)}.fly-topbar__brand:hover,.fly-topbar__module:hover{background:var(--bg-hover)}.fly-topbar__brand:focus-visible,.fly-topbar__module:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fly-topbar__brand-name{font-weight:var(--fw-semibold);white-space:nowrap}.fly-topbar__brand-mark{display:inline-flex;align-items:center;max-block-size:var(--icon-lg)}.fly-topbar__sep{color:var(--ink-4);-webkit-user-select:none;user-select:none}.fly-topbar__module{min-inline-size:0}.fly-topbar__module--on{background:var(--bg-hover)}.fly-topbar__module-label{font-weight:var(--fw-medium);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-topbar__module-label--empty{color:var(--ink-3)}.fly-topbar__module-icon,.fly-topbar__item-icon{display:inline-flex;align-items:center;justify-content:center;inline-size:var(--icon-md);block-size:var(--icon-md);flex:0 0 auto;color:var(--accent)}.fly-topbar__chevron{flex:0 0 auto;color:var(--ink-3);transition:transform var(--t-state)}.fly-topbar__module--on .fly-topbar__chevron{transform:rotate(180deg)}.fly-topbar__anchor{position:relative}:host(.fly-topbar--docked){flex:1 1 auto;min-inline-size:0;z-index:auto}:host(.fly-topbar--docked) .fly-topbar{padding:0;background:transparent;border-block-end:0}:host(.fly-topbar--docked) .fly-topbar__lead{gap:2px}:host(.fly-topbar--docked) .fly-topbar__brand,:host(.fly-topbar--docked) .fly-topbar__module{block-size:26px;padding-block:0;padding-inline:var(--sp-2);border-radius:var(--r-sm);font-size:var(--text-sm);line-height:1;color:var(--label-secondary)}:host(.fly-topbar--docked) .fly-topbar__brand:hover,:host(.fly-topbar--docked) .fly-topbar__module:hover{background:var(--w08);color:var(--label-primary)}:host(.fly-topbar--docked) .fly-topbar__module{color:var(--label-primary)}:host(.fly-topbar--docked) .fly-topbar__module--on{background:var(--w08)}:host(.fly-topbar--docked) .fly-topbar__brand-name{font-weight:var(--fw-medium)}:host(.fly-topbar--docked) .fly-topbar__module-label{font-weight:var(--fw-semibold)}:host(.fly-topbar--docked) .fly-topbar__sep{color:var(--w28)}:host(.fly-topbar--docked) .fly-topbar__module-icon{inline-size:14px;block-size:14px}:host(.fly-topbar--docked) .fly-topbar__brand-mark{max-block-size:22px}:host(.fly-topbar--docked) .fly-topbar__brand-mark ::ng-deep *{max-inline-size:22px;max-block-size:22px}.fly-topbar__pop{position:absolute;inset-block-start:calc(100% + var(--sp-1));inset-inline-start:0;z-index:var(--z-overlay);min-inline-size:260px;max-inline-size:min(680px,92vw);padding:var(--sp-3);border-radius:var(--r-lg);border:1px solid var(--glass2-border);background-image:radial-gradient(70% 60% at 50% 100%,var(--w07),transparent 70%),linear-gradient(180deg,var(--w08),transparent 20%),linear-gradient(180deg,var(--mat-menu-solid-a),var(--mat-menu-solid-b));box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset)}@media(prefers-reduced-transparency:reduce){.fly-topbar__pop{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.fly-topbar__pop:before,.fly-topbar__pop:after{display:none}}@media(prefers-contrast:more){.fly-topbar__pop{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.fly-topbar__pop:after{animation:none}}.fly-topbar__cols{display:flex;gap:var(--sp-4);align-items:flex-start}@media(width<=720px){.fly-topbar__cols{flex-direction:column;gap:var(--sp-3)}}.fly-topbar__col{display:flex;flex-direction:column;gap:2px;min-inline-size:220px;flex:1 1 0}.fly-topbar__col-title{font-size:var(--text-2xs);font-weight:var(--fw-semibold);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-3);padding:var(--sp-2) var(--sp-2) var(--sp-1)}.fly-topbar__item{display:flex;align-items:flex-start;gap:var(--sp-2);inline-size:100%;text-align:start;appearance:none;border:0;background:transparent;cursor:pointer;font:inherit;color:var(--ink);padding:var(--sp-2);border-radius:var(--r-md);transition:background var(--t-state)}.fly-topbar__item:hover:not(:disabled){background:var(--w1)}.fly-topbar__item:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-2px}.fly-topbar__item:disabled{opacity:.5;cursor:default}.fly-topbar__item--on{background:var(--accent-soft);color:var(--on-accent-soft);box-shadow:inset 2px 0 0 0 var(--accent)}.fly-topbar__item-main{display:flex;flex-direction:column;gap:1px;min-inline-size:0}.fly-topbar__item-label{font-weight:var(--fw-medium);line-height:1.3}.fly-topbar__item-desc{font-size:var(--text-xs);color:var(--ink-3);line-height:1.35}.fly-topbar__foot{margin-block-start:var(--sp-3);padding-block-start:var(--sp-2);border-block-start:.5px solid var(--w08);font-size:var(--text-2xs);color:var(--ink-4)}.fly-topbar__actions{display:flex;align-items:center;gap:var(--sp-2);flex:0 0 auto}@media(forced-colors:active){.fly-topbar__item--on{border-inline-start:2px solid CanvasText}}@media(prefers-reduced-motion:reduce){.fly-topbar__brand,.fly-topbar__module,.fly-topbar__item,.fly-topbar__chevron{transition:none}}\n"], dependencies: [{ kind: "directive", type: FlyClickOutsideDirective, selector: "[flyClickOutside]", inputs: ["flyClickOutsideEnabled"], outputs: ["flyClickOutside"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
23978
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlyAppTopbarComponent, isStandalone: true, selector: "fly-app-topbar", inputs: { brandLabelKey: { classPropertyName: "brandLabelKey", publicName: "brandLabelKey", isSignal: true, isRequired: false, transformFunction: null }, sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: false, transformFunction: null }, activeKey: { classPropertyName: "activeKey", publicName: "activeKey", isSignal: true, isRequired: false, transformFunction: null }, footerLabel: { classPropertyName: "footerLabel", publicName: "footerLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { moduleSelected: "moduleSelected", brandSelected: "brandSelected" }, host: { listeners: { "document:keydown.escape": "onEscape()" }, properties: { "class.fly-topbar--docked": "docked()" } }, queries: [{ propertyName: "icons", predicate: FlyModuleIconDirective, isSignal: true }], viewQueries: [{ propertyName: "rows", predicate: ["row"], descendants: true, isSignal: true }, { propertyName: "trigger", predicate: ["trigger"], descendants: true, isSignal: true }], ngImport: i0, template: "<header class=\"fly-topbar\">\r\n <div class=\"fly-topbar__lead\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-topbar__brand\"\r\n (click)=\"brandSelected.emit()\"\r\n [attr.aria-label]=\"brandAriaLabel()\"\r\n >\r\n <span class=\"fly-topbar__brand-mark\"><ng-content select=\"[app-topbar-brand]\" /></span>\r\n @if (brandLabelKey(); as key) {\r\n <span class=\"fly-topbar__brand-name\">{{ key | translate }}</span>\r\n }\r\n </button>\r\n\r\n @if (activeModule()) {\r\n <span class=\"fly-topbar__sep\" aria-hidden=\"true\">/</span>\r\n }\r\n\r\n <div class=\"fly-topbar__anchor\" (flyClickOutside)=\"close()\" [flyClickOutsideEnabled]=\"open()\">\r\n <button\r\n #trigger\r\n type=\"button\"\r\n class=\"fly-topbar__module\"\r\n [class.fly-topbar__module--on]=\"open()\"\r\n (click)=\"toggle()\"\r\n (keydown)=\"onTriggerKeydown($event)\"\r\n aria-haspopup=\"menu\"\r\n [attr.aria-expanded]=\"open()\"\r\n [attr.aria-label]=\"triggerAriaLabel()\"\r\n >\r\n @if (activeModule(); as active) {\r\n <span class=\"fly-topbar__module-icon\">\r\n @if (iconFor(active.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (active.iconClass) {\r\n <i [class]=\"active.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n <span class=\"fly-topbar__module-label\">{{ active.labelKey | translate }}</span>\r\n } @else {\r\n <span class=\"fly-topbar__module-label fly-topbar__module-label--empty\">\r\n {{ 'ui.nav.selectModule' | translate }}\r\n </span>\r\n }\r\n <svg class=\"fly-topbar__chevron\" width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\">\r\n <polyline points=\"6 9 12 15 18 9\" />\r\n </svg>\r\n </button>\r\n\r\n @if (open()) {\r\n <div class=\"fly-topbar__pop\" role=\"menu\" [attr.aria-label]=\"switchAriaLabel()\">\r\n <div class=\"fly-topbar__cols\">\r\n @for (section of sections(); track $index) {\r\n <div class=\"fly-topbar__col\">\r\n @if (section.titleKey; as titleKey) {\r\n <div class=\"fly-topbar__col-title\">{{ titleKey | translate }}</div>\r\n }\r\n @for (mod of section.modules; track mod.key) {\r\n <button\r\n #row\r\n type=\"button\"\r\n class=\"fly-topbar__item\"\r\n [class.fly-topbar__item--on]=\"mod.key === activeKey()\"\r\n role=\"menuitem\"\r\n [disabled]=\"mod.disabled ?? false\"\r\n [attr.tabindex]=\"indexOf(mod) === focusIndex() ? 0 : -1\"\r\n [attr.aria-current]=\"mod.key === activeKey() ? 'page' : null\"\r\n (click)=\"select(mod)\"\r\n (keydown)=\"onRowKeydown($event)\"\r\n >\r\n <span class=\"fly-topbar__item-icon\">\r\n @if (iconFor(mod.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (mod.iconClass) {\r\n <i [class]=\"mod.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n <span class=\"fly-topbar__item-main\">\r\n <span class=\"fly-topbar__item-label\">{{ mod.labelKey | translate }}</span>\r\n @if (mod.descKey; as descKey) {\r\n <span class=\"fly-topbar__item-desc\">{{ descKey | translate }}</span>\r\n }\r\n </span>\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n @if (footerLabel()) {\r\n <div class=\"fly-topbar__foot\">{{ footerLabel() }}</div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"fly-topbar__actions\"><ng-content select=\"[app-topbar-actions]\" /></div>\r\n</header>\r\n", styles: [":host{display:block;position:relative;z-index:var(--z-sticky)}.fly-topbar{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-3);padding:var(--sp-2) var(--sp-3);border-block-end:1px solid var(--w1);background:var(--bg-3)}.fly-topbar__lead{display:flex;align-items:center;gap:var(--sp-2);min-inline-size:0}.fly-topbar__brand,.fly-topbar__module{display:inline-flex;align-items:center;gap:var(--sp-2);appearance:none;border:0;background:transparent;cursor:pointer;font:inherit;color:var(--ink);padding:var(--sp-1) var(--sp-2);border-radius:var(--r-md);transition:background var(--t-state)}.fly-topbar__brand:hover,.fly-topbar__module:hover{background:var(--bg-hover)}.fly-topbar__brand:focus-visible,.fly-topbar__module:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fly-topbar__brand-name{font-weight:var(--fw-semibold);white-space:nowrap}.fly-topbar__brand-mark{display:inline-flex;align-items:center;max-block-size:var(--icon-lg)}.fly-topbar__sep{color:var(--ink-4);-webkit-user-select:none;user-select:none}.fly-topbar__module{min-inline-size:0}.fly-topbar__module--on{background:var(--bg-hover)}.fly-topbar__module-label{font-weight:var(--fw-medium);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-topbar__module-label--empty{color:var(--ink-3)}.fly-topbar__module-icon,.fly-topbar__item-icon{display:inline-flex;align-items:center;justify-content:center;inline-size:var(--icon-md);block-size:var(--icon-md);flex:0 0 auto;color:var(--accent)}.fly-topbar__chevron{flex:0 0 auto;color:var(--ink-3);transition:transform var(--t-state)}.fly-topbar__module--on .fly-topbar__chevron{transform:rotate(180deg)}.fly-topbar__anchor{position:relative}:host(.fly-topbar--docked){flex:1 1 auto;min-inline-size:0;z-index:auto}:host(.fly-topbar--docked) .fly-topbar{padding:0;background:transparent;border-block-end:0}:host(.fly-topbar--docked) .fly-topbar__lead{gap:2px}:host(.fly-topbar--docked) .fly-topbar__brand,:host(.fly-topbar--docked) .fly-topbar__module{block-size:26px;padding-block:0;padding-inline:var(--sp-2);border-radius:var(--r-md);gap:6px;font-size:var(--text-sm);line-height:1;color:var(--w55)}:host(.fly-topbar--docked) .fly-topbar__brand:hover,:host(.fly-topbar--docked) .fly-topbar__module:hover{background:var(--w08);color:var(--label-primary)}:host(.fly-topbar--docked) .fly-topbar__module{background:var(--w06);color:var(--label-primary)}:host(.fly-topbar--docked) .fly-topbar__module--on{background:var(--w08)}:host(.fly-topbar--docked) .fly-topbar__brand-name{font-weight:var(--fw-medium)}:host(.fly-topbar--docked) .fly-topbar__module-label{font-weight:var(--fw-semibold);letter-spacing:-.006em}:host-context([dir=rtl]) :host(.fly-topbar--docked) .fly-topbar__module-label{letter-spacing:normal}:host(.fly-topbar--docked) .fly-topbar__sep{position:relative;display:inline-block;inline-size:7px;block-size:7px;color:transparent;border-block-start:1.5px solid var(--w28);border-inline-end:1.5px solid var(--w28);transform:rotate(45deg)}:host-context([dir=rtl]) :host(.fly-topbar--docked) .fly-topbar__sep{transform:rotate(-135deg)}:host(.fly-topbar--docked) .fly-topbar__actions{display:none}:host(.fly-topbar--docked) .fly-topbar__module-icon{inline-size:14px;block-size:14px}:host(.fly-topbar--docked) .fly-topbar__brand-mark{max-block-size:14px}:host(.fly-topbar--docked) .fly-topbar__brand-mark ::ng-deep *{max-inline-size:14px;max-block-size:14px}.fly-topbar__pop{position:absolute;inset-block-start:calc(100% + var(--sp-1));inset-inline-start:0;z-index:var(--z-overlay);min-inline-size:260px;max-inline-size:min(680px,92vw);padding:var(--sp-3);border-radius:var(--r-lg);border:1px solid var(--glass2-border);background-image:radial-gradient(70% 60% at 50% 100%,var(--w07),transparent 70%),linear-gradient(180deg,var(--w08),transparent 20%),linear-gradient(180deg,var(--mat-menu-solid-a),var(--mat-menu-solid-b));box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset)}@media(prefers-reduced-transparency:reduce){.fly-topbar__pop{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.fly-topbar__pop:before,.fly-topbar__pop:after{display:none}}@media(prefers-contrast:more){.fly-topbar__pop{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.fly-topbar__pop:after{animation:none}}.fly-topbar__cols{display:flex;gap:var(--sp-4);align-items:flex-start}@media(width<=720px){.fly-topbar__cols{flex-direction:column;gap:var(--sp-3)}}.fly-topbar__col{display:flex;flex-direction:column;gap:2px;min-inline-size:220px;flex:1 1 0}.fly-topbar__col-title{font-size:var(--text-2xs);font-weight:var(--fw-semibold);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-3);padding:var(--sp-2) var(--sp-2) var(--sp-1)}.fly-topbar__item{display:flex;align-items:flex-start;gap:var(--sp-2);inline-size:100%;text-align:start;appearance:none;border:0;background:transparent;cursor:pointer;font:inherit;color:var(--ink);padding:var(--sp-2);border-radius:var(--r-md);transition:background var(--t-state)}.fly-topbar__item:hover:not(:disabled){background:var(--w1)}.fly-topbar__item:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-2px}.fly-topbar__item:disabled{opacity:.5;cursor:default}.fly-topbar__item--on{background:var(--accent-soft);color:var(--on-accent-soft);box-shadow:inset 2px 0 0 0 var(--accent)}.fly-topbar__item-main{display:flex;flex-direction:column;gap:1px;min-inline-size:0}.fly-topbar__item-label{font-weight:var(--fw-medium);line-height:1.3}.fly-topbar__item-desc{font-size:var(--text-xs);color:var(--ink-3);line-height:1.35}.fly-topbar__foot{margin-block-start:var(--sp-3);padding-block-start:var(--sp-2);border-block-start:.5px solid var(--w08);font-size:var(--text-2xs);color:var(--ink-4)}.fly-topbar__actions{display:flex;align-items:center;gap:var(--sp-2);flex:0 0 auto}@media(forced-colors:active){.fly-topbar__item--on{border-inline-start:2px solid CanvasText}}@media(prefers-reduced-motion:reduce){.fly-topbar__brand,.fly-topbar__module,.fly-topbar__item,.fly-topbar__chevron{transition:none}}\n"], dependencies: [{ kind: "directive", type: FlyClickOutsideDirective, selector: "[flyClickOutside]", inputs: ["flyClickOutsideEnabled", "flyClickOutsideIgnore"], outputs: ["flyClickOutside"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
23296
23979
|
}
|
|
23297
23980
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlyAppTopbarComponent, decorators: [{
|
|
23298
23981
|
type: Component,
|
|
23299
23982
|
args: [{ selector: 'fly-app-topbar', standalone: true, imports: [TranslatePipe, FlyClickOutsideDirective, NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
23300
23983
|
'(document:keydown.escape)': 'onEscape()',
|
|
23301
23984
|
'[class.fly-topbar--docked]': 'docked()',
|
|
23302
|
-
}, template: "<header class=\"fly-topbar\">\r\n <div class=\"fly-topbar__lead\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-topbar__brand\"\r\n (click)=\"brandSelected.emit()\"\r\n [attr.aria-label]=\"brandAriaLabel()\"\r\n >\r\n <span class=\"fly-topbar__brand-mark\"><ng-content select=\"[app-topbar-brand]\" /></span>\r\n @if (brandLabelKey(); as key) {\r\n <span class=\"fly-topbar__brand-name\">{{ key | translate }}</span>\r\n }\r\n </button>\r\n\r\n @if (activeModule()) {\r\n <span class=\"fly-topbar__sep\" aria-hidden=\"true\">/</span>\r\n }\r\n\r\n <div class=\"fly-topbar__anchor\" (flyClickOutside)=\"close()\" [flyClickOutsideEnabled]=\"open()\">\r\n <button\r\n #trigger\r\n type=\"button\"\r\n class=\"fly-topbar__module\"\r\n [class.fly-topbar__module--on]=\"open()\"\r\n (click)=\"toggle()\"\r\n (keydown)=\"onTriggerKeydown($event)\"\r\n aria-haspopup=\"menu\"\r\n [attr.aria-expanded]=\"open()\"\r\n [attr.aria-label]=\"triggerAriaLabel()\"\r\n >\r\n @if (activeModule(); as active) {\r\n <span class=\"fly-topbar__module-icon\">\r\n @if (iconFor(active.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (active.iconClass) {\r\n <i [class]=\"active.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n <span class=\"fly-topbar__module-label\">{{ active.labelKey | translate }}</span>\r\n } @else {\r\n <span class=\"fly-topbar__module-label fly-topbar__module-label--empty\">\r\n {{ 'ui.nav.selectModule' | translate }}\r\n </span>\r\n }\r\n <svg class=\"fly-topbar__chevron\" width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\">\r\n <polyline points=\"6 9 12 15 18 9\" />\r\n </svg>\r\n </button>\r\n\r\n @if (open()) {\r\n <div class=\"fly-topbar__pop\" role=\"menu\" [attr.aria-label]=\"switchAriaLabel()\">\r\n <div class=\"fly-topbar__cols\">\r\n @for (section of sections(); track $index) {\r\n <div class=\"fly-topbar__col\">\r\n @if (section.titleKey; as titleKey) {\r\n <div class=\"fly-topbar__col-title\">{{ titleKey | translate }}</div>\r\n }\r\n @for (mod of section.modules; track mod.key) {\r\n <button\r\n #row\r\n type=\"button\"\r\n class=\"fly-topbar__item\"\r\n [class.fly-topbar__item--on]=\"mod.key === activeKey()\"\r\n role=\"menuitem\"\r\n [disabled]=\"mod.disabled ?? false\"\r\n [attr.tabindex]=\"indexOf(mod) === focusIndex() ? 0 : -1\"\r\n [attr.aria-current]=\"mod.key === activeKey() ? 'page' : null\"\r\n (click)=\"select(mod)\"\r\n (keydown)=\"onRowKeydown($event)\"\r\n >\r\n <span class=\"fly-topbar__item-icon\">\r\n @if (iconFor(mod.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (mod.iconClass) {\r\n <i [class]=\"mod.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n <span class=\"fly-topbar__item-main\">\r\n <span class=\"fly-topbar__item-label\">{{ mod.labelKey | translate }}</span>\r\n @if (mod.descKey; as descKey) {\r\n <span class=\"fly-topbar__item-desc\">{{ descKey | translate }}</span>\r\n }\r\n </span>\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n @if (footerLabel()) {\r\n <div class=\"fly-topbar__foot\">{{ footerLabel() }}</div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"fly-topbar__actions\"><ng-content select=\"[app-topbar-actions]\" /></div>\r\n</header>\r\n", styles: [":host{display:block;position:relative;z-index:var(--z-sticky)}.fly-topbar{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-3);padding:var(--sp-2) var(--sp-3);border-block-end:1px solid var(--w1);background:var(--bg-3)}.fly-topbar__lead{display:flex;align-items:center;gap:var(--sp-2);min-inline-size:0}.fly-topbar__brand,.fly-topbar__module{display:inline-flex;align-items:center;gap:var(--sp-2);appearance:none;border:0;background:transparent;cursor:pointer;font:inherit;color:var(--ink);padding:var(--sp-1) var(--sp-2);border-radius:var(--r-md);transition:background var(--t-state)}.fly-topbar__brand:hover,.fly-topbar__module:hover{background:var(--bg-hover)}.fly-topbar__brand:focus-visible,.fly-topbar__module:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fly-topbar__brand-name{font-weight:var(--fw-semibold);white-space:nowrap}.fly-topbar__brand-mark{display:inline-flex;align-items:center;max-block-size:var(--icon-lg)}.fly-topbar__sep{color:var(--ink-4);-webkit-user-select:none;user-select:none}.fly-topbar__module{min-inline-size:0}.fly-topbar__module--on{background:var(--bg-hover)}.fly-topbar__module-label{font-weight:var(--fw-medium);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-topbar__module-label--empty{color:var(--ink-3)}.fly-topbar__module-icon,.fly-topbar__item-icon{display:inline-flex;align-items:center;justify-content:center;inline-size:var(--icon-md);block-size:var(--icon-md);flex:0 0 auto;color:var(--accent)}.fly-topbar__chevron{flex:0 0 auto;color:var(--ink-3);transition:transform var(--t-state)}.fly-topbar__module--on .fly-topbar__chevron{transform:rotate(180deg)}.fly-topbar__anchor{position:relative}:host(.fly-topbar--docked){flex:1 1 auto;min-inline-size:0;z-index:auto}:host(.fly-topbar--docked) .fly-topbar{padding:0;background:transparent;border-block-end:0}:host(.fly-topbar--docked) .fly-topbar__lead{gap:2px}:host(.fly-topbar--docked) .fly-topbar__brand,:host(.fly-topbar--docked) .fly-topbar__module{block-size:26px;padding-block:0;padding-inline:var(--sp-2);border-radius:var(--r-sm);font-size:var(--text-sm);line-height:1;color:var(--label-secondary)}:host(.fly-topbar--docked) .fly-topbar__brand:hover,:host(.fly-topbar--docked) .fly-topbar__module:hover{background:var(--w08);color:var(--label-primary)}:host(.fly-topbar--docked) .fly-topbar__module{color:var(--label-primary)}:host(.fly-topbar--docked) .fly-topbar__module--on{background:var(--w08)}:host(.fly-topbar--docked) .fly-topbar__brand-name{font-weight:var(--fw-medium)}:host(.fly-topbar--docked) .fly-topbar__module-label{font-weight:var(--fw-semibold)}:host(.fly-topbar--docked) .fly-topbar__sep{color:var(--w28)}:host(.fly-topbar--docked) .fly-topbar__module-icon{inline-size:14px;block-size:14px}:host(.fly-topbar--docked) .fly-topbar__brand-mark{max-block-size:22px}:host(.fly-topbar--docked) .fly-topbar__brand-mark ::ng-deep *{max-inline-size:22px;max-block-size:22px}.fly-topbar__pop{position:absolute;inset-block-start:calc(100% + var(--sp-1));inset-inline-start:0;z-index:var(--z-overlay);min-inline-size:260px;max-inline-size:min(680px,92vw);padding:var(--sp-3);border-radius:var(--r-lg);border:1px solid var(--glass2-border);background-image:radial-gradient(70% 60% at 50% 100%,var(--w07),transparent 70%),linear-gradient(180deg,var(--w08),transparent 20%),linear-gradient(180deg,var(--mat-menu-solid-a),var(--mat-menu-solid-b));box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset)}@media(prefers-reduced-transparency:reduce){.fly-topbar__pop{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.fly-topbar__pop:before,.fly-topbar__pop:after{display:none}}@media(prefers-contrast:more){.fly-topbar__pop{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.fly-topbar__pop:after{animation:none}}.fly-topbar__cols{display:flex;gap:var(--sp-4);align-items:flex-start}@media(width<=720px){.fly-topbar__cols{flex-direction:column;gap:var(--sp-3)}}.fly-topbar__col{display:flex;flex-direction:column;gap:2px;min-inline-size:220px;flex:1 1 0}.fly-topbar__col-title{font-size:var(--text-2xs);font-weight:var(--fw-semibold);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-3);padding:var(--sp-2) var(--sp-2) var(--sp-1)}.fly-topbar__item{display:flex;align-items:flex-start;gap:var(--sp-2);inline-size:100%;text-align:start;appearance:none;border:0;background:transparent;cursor:pointer;font:inherit;color:var(--ink);padding:var(--sp-2);border-radius:var(--r-md);transition:background var(--t-state)}.fly-topbar__item:hover:not(:disabled){background:var(--w1)}.fly-topbar__item:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-2px}.fly-topbar__item:disabled{opacity:.5;cursor:default}.fly-topbar__item--on{background:var(--accent-soft);color:var(--on-accent-soft);box-shadow:inset 2px 0 0 0 var(--accent)}.fly-topbar__item-main{display:flex;flex-direction:column;gap:1px;min-inline-size:0}.fly-topbar__item-label{font-weight:var(--fw-medium);line-height:1.3}.fly-topbar__item-desc{font-size:var(--text-xs);color:var(--ink-3);line-height:1.35}.fly-topbar__foot{margin-block-start:var(--sp-3);padding-block-start:var(--sp-2);border-block-start:.5px solid var(--w08);font-size:var(--text-2xs);color:var(--ink-4)}.fly-topbar__actions{display:flex;align-items:center;gap:var(--sp-2);flex:0 0 auto}@media(forced-colors:active){.fly-topbar__item--on{border-inline-start:2px solid CanvasText}}@media(prefers-reduced-motion:reduce){.fly-topbar__brand,.fly-topbar__module,.fly-topbar__item,.fly-topbar__chevron{transition:none}}\n"] }]
|
|
23985
|
+
}, template: "<header class=\"fly-topbar\">\r\n <div class=\"fly-topbar__lead\">\r\n <button\r\n type=\"button\"\r\n class=\"fly-topbar__brand\"\r\n (click)=\"brandSelected.emit()\"\r\n [attr.aria-label]=\"brandAriaLabel()\"\r\n >\r\n <span class=\"fly-topbar__brand-mark\"><ng-content select=\"[app-topbar-brand]\" /></span>\r\n @if (brandLabelKey(); as key) {\r\n <span class=\"fly-topbar__brand-name\">{{ key | translate }}</span>\r\n }\r\n </button>\r\n\r\n @if (activeModule()) {\r\n <span class=\"fly-topbar__sep\" aria-hidden=\"true\">/</span>\r\n }\r\n\r\n <div class=\"fly-topbar__anchor\" (flyClickOutside)=\"close()\" [flyClickOutsideEnabled]=\"open()\">\r\n <button\r\n #trigger\r\n type=\"button\"\r\n class=\"fly-topbar__module\"\r\n [class.fly-topbar__module--on]=\"open()\"\r\n (click)=\"toggle()\"\r\n (keydown)=\"onTriggerKeydown($event)\"\r\n aria-haspopup=\"menu\"\r\n [attr.aria-expanded]=\"open()\"\r\n [attr.aria-label]=\"triggerAriaLabel()\"\r\n >\r\n @if (activeModule(); as active) {\r\n <span class=\"fly-topbar__module-icon\">\r\n @if (iconFor(active.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (active.iconClass) {\r\n <i [class]=\"active.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n <span class=\"fly-topbar__module-label\">{{ active.labelKey | translate }}</span>\r\n } @else {\r\n <span class=\"fly-topbar__module-label fly-topbar__module-label--empty\">\r\n {{ 'ui.nav.selectModule' | translate }}\r\n </span>\r\n }\r\n <svg class=\"fly-topbar__chevron\" width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\r\n aria-hidden=\"true\">\r\n <polyline points=\"6 9 12 15 18 9\" />\r\n </svg>\r\n </button>\r\n\r\n @if (open()) {\r\n <div class=\"fly-topbar__pop\" role=\"menu\" [attr.aria-label]=\"switchAriaLabel()\">\r\n <div class=\"fly-topbar__cols\">\r\n @for (section of sections(); track $index) {\r\n <div class=\"fly-topbar__col\">\r\n @if (section.titleKey; as titleKey) {\r\n <div class=\"fly-topbar__col-title\">{{ titleKey | translate }}</div>\r\n }\r\n @for (mod of section.modules; track mod.key) {\r\n <button\r\n #row\r\n type=\"button\"\r\n class=\"fly-topbar__item\"\r\n [class.fly-topbar__item--on]=\"mod.key === activeKey()\"\r\n role=\"menuitem\"\r\n [disabled]=\"mod.disabled ?? false\"\r\n [attr.tabindex]=\"indexOf(mod) === focusIndex() ? 0 : -1\"\r\n [attr.aria-current]=\"mod.key === activeKey() ? 'page' : null\"\r\n (click)=\"select(mod)\"\r\n (keydown)=\"onRowKeydown($event)\"\r\n >\r\n <span class=\"fly-topbar__item-icon\">\r\n @if (iconFor(mod.key); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl\" />\r\n } @else if (mod.iconClass) {\r\n <i [class]=\"mod.iconClass\" aria-hidden=\"true\"></i>\r\n }\r\n </span>\r\n <span class=\"fly-topbar__item-main\">\r\n <span class=\"fly-topbar__item-label\">{{ mod.labelKey | translate }}</span>\r\n @if (mod.descKey; as descKey) {\r\n <span class=\"fly-topbar__item-desc\">{{ descKey | translate }}</span>\r\n }\r\n </span>\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n @if (footerLabel()) {\r\n <div class=\"fly-topbar__foot\">{{ footerLabel() }}</div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"fly-topbar__actions\"><ng-content select=\"[app-topbar-actions]\" /></div>\r\n</header>\r\n", styles: [":host{display:block;position:relative;z-index:var(--z-sticky)}.fly-topbar{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-3);padding:var(--sp-2) var(--sp-3);border-block-end:1px solid var(--w1);background:var(--bg-3)}.fly-topbar__lead{display:flex;align-items:center;gap:var(--sp-2);min-inline-size:0}.fly-topbar__brand,.fly-topbar__module{display:inline-flex;align-items:center;gap:var(--sp-2);appearance:none;border:0;background:transparent;cursor:pointer;font:inherit;color:var(--ink);padding:var(--sp-1) var(--sp-2);border-radius:var(--r-md);transition:background var(--t-state)}.fly-topbar__brand:hover,.fly-topbar__module:hover{background:var(--bg-hover)}.fly-topbar__brand:focus-visible,.fly-topbar__module:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}.fly-topbar__brand-name{font-weight:var(--fw-semibold);white-space:nowrap}.fly-topbar__brand-mark{display:inline-flex;align-items:center;max-block-size:var(--icon-lg)}.fly-topbar__sep{color:var(--ink-4);-webkit-user-select:none;user-select:none}.fly-topbar__module{min-inline-size:0}.fly-topbar__module--on{background:var(--bg-hover)}.fly-topbar__module-label{font-weight:var(--fw-medium);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fly-topbar__module-label--empty{color:var(--ink-3)}.fly-topbar__module-icon,.fly-topbar__item-icon{display:inline-flex;align-items:center;justify-content:center;inline-size:var(--icon-md);block-size:var(--icon-md);flex:0 0 auto;color:var(--accent)}.fly-topbar__chevron{flex:0 0 auto;color:var(--ink-3);transition:transform var(--t-state)}.fly-topbar__module--on .fly-topbar__chevron{transform:rotate(180deg)}.fly-topbar__anchor{position:relative}:host(.fly-topbar--docked){flex:1 1 auto;min-inline-size:0;z-index:auto}:host(.fly-topbar--docked) .fly-topbar{padding:0;background:transparent;border-block-end:0}:host(.fly-topbar--docked) .fly-topbar__lead{gap:2px}:host(.fly-topbar--docked) .fly-topbar__brand,:host(.fly-topbar--docked) .fly-topbar__module{block-size:26px;padding-block:0;padding-inline:var(--sp-2);border-radius:var(--r-md);gap:6px;font-size:var(--text-sm);line-height:1;color:var(--w55)}:host(.fly-topbar--docked) .fly-topbar__brand:hover,:host(.fly-topbar--docked) .fly-topbar__module:hover{background:var(--w08);color:var(--label-primary)}:host(.fly-topbar--docked) .fly-topbar__module{background:var(--w06);color:var(--label-primary)}:host(.fly-topbar--docked) .fly-topbar__module--on{background:var(--w08)}:host(.fly-topbar--docked) .fly-topbar__brand-name{font-weight:var(--fw-medium)}:host(.fly-topbar--docked) .fly-topbar__module-label{font-weight:var(--fw-semibold);letter-spacing:-.006em}:host-context([dir=rtl]) :host(.fly-topbar--docked) .fly-topbar__module-label{letter-spacing:normal}:host(.fly-topbar--docked) .fly-topbar__sep{position:relative;display:inline-block;inline-size:7px;block-size:7px;color:transparent;border-block-start:1.5px solid var(--w28);border-inline-end:1.5px solid var(--w28);transform:rotate(45deg)}:host-context([dir=rtl]) :host(.fly-topbar--docked) .fly-topbar__sep{transform:rotate(-135deg)}:host(.fly-topbar--docked) .fly-topbar__actions{display:none}:host(.fly-topbar--docked) .fly-topbar__module-icon{inline-size:14px;block-size:14px}:host(.fly-topbar--docked) .fly-topbar__brand-mark{max-block-size:14px}:host(.fly-topbar--docked) .fly-topbar__brand-mark ::ng-deep *{max-inline-size:14px;max-block-size:14px}.fly-topbar__pop{position:absolute;inset-block-start:calc(100% + var(--sp-1));inset-inline-start:0;z-index:var(--z-overlay);min-inline-size:260px;max-inline-size:min(680px,92vw);padding:var(--sp-3);border-radius:var(--r-lg);border:1px solid var(--glass2-border);background-image:radial-gradient(70% 60% at 50% 100%,var(--w07),transparent 70%),linear-gradient(180deg,var(--w08),transparent 20%),linear-gradient(180deg,var(--mat-menu-solid-a),var(--mat-menu-solid-b));box-shadow:0 16px 40px #0003,0 4px 10px #0000001a,var(--glass2-inset)}@media(prefers-reduced-transparency:reduce){.fly-topbar__pop{border-color:transparent;background-color:light-dark(#eef2f8,#2c2c2e);background-image:none;box-shadow:none;-webkit-backdrop-filter:none;backdrop-filter:none}.fly-topbar__pop:before,.fly-topbar__pop:after{display:none}}@media(prefers-contrast:more){.fly-topbar__pop{border-color:var(--w6)}}@media(prefers-reduced-motion:reduce){.fly-topbar__pop:after{animation:none}}.fly-topbar__cols{display:flex;gap:var(--sp-4);align-items:flex-start}@media(width<=720px){.fly-topbar__cols{flex-direction:column;gap:var(--sp-3)}}.fly-topbar__col{display:flex;flex-direction:column;gap:2px;min-inline-size:220px;flex:1 1 0}.fly-topbar__col-title{font-size:var(--text-2xs);font-weight:var(--fw-semibold);letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-3);padding:var(--sp-2) var(--sp-2) var(--sp-1)}.fly-topbar__item{display:flex;align-items:flex-start;gap:var(--sp-2);inline-size:100%;text-align:start;appearance:none;border:0;background:transparent;cursor:pointer;font:inherit;color:var(--ink);padding:var(--sp-2);border-radius:var(--r-md);transition:background var(--t-state)}.fly-topbar__item:hover:not(:disabled){background:var(--w1)}.fly-topbar__item:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-2px}.fly-topbar__item:disabled{opacity:.5;cursor:default}.fly-topbar__item--on{background:var(--accent-soft);color:var(--on-accent-soft);box-shadow:inset 2px 0 0 0 var(--accent)}.fly-topbar__item-main{display:flex;flex-direction:column;gap:1px;min-inline-size:0}.fly-topbar__item-label{font-weight:var(--fw-medium);line-height:1.3}.fly-topbar__item-desc{font-size:var(--text-xs);color:var(--ink-3);line-height:1.35}.fly-topbar__foot{margin-block-start:var(--sp-3);padding-block-start:var(--sp-2);border-block-start:.5px solid var(--w08);font-size:var(--text-2xs);color:var(--ink-4)}.fly-topbar__actions{display:flex;align-items:center;gap:var(--sp-2);flex:0 0 auto}@media(forced-colors:active){.fly-topbar__item--on{border-inline-start:2px solid CanvasText}}@media(prefers-reduced-motion:reduce){.fly-topbar__brand,.fly-topbar__module,.fly-topbar__item,.fly-topbar__chevron{transition:none}}\n"] }]
|
|
23303
23986
|
}], ctorParameters: () => [], propDecorators: { brandLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "brandLabelKey", required: false }] }], sections: [{ type: i0.Input, args: [{ isSignal: true, alias: "sections", required: false }] }], activeKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeKey", required: false }] }], footerLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "footerLabel", required: false }] }], moduleSelected: [{ type: i0.Output, args: ["moduleSelected"] }], brandSelected: [{ type: i0.Output, args: ["brandSelected"] }], icons: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => FlyModuleIconDirective), { isSignal: true }] }], rows: [{ type: i0.ViewChildren, args: ['row', { isSignal: true }] }], trigger: [{ type: i0.ViewChildren, args: ['trigger', { isSignal: true }] }] } });
|
|
23304
23987
|
|
|
23305
23988
|
/**
|
|
@@ -23942,13 +24625,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
|
|
|
23942
24625
|
}], propDecorators: { checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], labelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelKey", required: false }] }], hintKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "hintKey", required: false }] }] } });
|
|
23943
24626
|
|
|
23944
24627
|
/**
|
|
23945
|
-
* Segmented
|
|
23946
|
-
*
|
|
23947
|
-
*
|
|
24628
|
+
* Segmented control — one anatomy for form segmented controls (the llm-throttle
|
|
24629
|
+
* level picker), list status-pill filters (the legacy `.signals-page-statusfilter`,
|
|
24630
|
+
* which is also the visual source), and the canon filter-panel status row.
|
|
23948
24631
|
*
|
|
23949
|
-
*
|
|
23950
|
-
* (
|
|
23951
|
-
*
|
|
24632
|
+
* ## Two selection models
|
|
24633
|
+
* Single-select (default) is a `role="radiogroup"` over `role="radio"` segments,
|
|
24634
|
+
* driven by {@link value}. Multi-select ({@link multiple}) is a `role="group"` of
|
|
24635
|
+
* `aria-pressed` toggle buttons, driven by {@link values}. Both are here rather than
|
|
24636
|
+
* split across two components because everything a reader has to learn — the option
|
|
24637
|
+
* list, the count badge, the two variants, the RTL handling — is shared, and a filter
|
|
24638
|
+
* row routinely needs the multi-select one while looking identical.
|
|
24639
|
+
*
|
|
24640
|
+
* The a11y models genuinely differ and the branch is not cosmetic: a radiogroup is
|
|
24641
|
+
* ONE tab stop with arrow keys moving the selection, while a group of toggle buttons
|
|
24642
|
+
* is individually tabbable and arrow keys must not change anything (moving selection
|
|
24643
|
+
* on arrow would silently toggle filters as a keyboard user walked the row).
|
|
24644
|
+
*
|
|
24645
|
+
* a11y: roving tabindex in single-select (the selected segment is the tab stop),
|
|
24646
|
+
* RTL-aware arrow-key navigation with wrap-around; arrows move both selection and
|
|
24647
|
+
* focus. Multi-select leaves the native tab order alone and binds no arrow keys.
|
|
23952
24648
|
*/
|
|
23953
24649
|
class FlySegmentedComponent {
|
|
23954
24650
|
i18n = inject(I18nService);
|
|
@@ -23959,17 +24655,48 @@ class FlySegmentedComponent {
|
|
|
23959
24655
|
value = model(undefined, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
23960
24656
|
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
23961
24657
|
ariaLabelKey = input(...(ngDevMode ? [undefined, { debugName: "ariaLabelKey" }] : /* istanbul ignore next */ []));
|
|
24658
|
+
/** See {@link SegmentedVariant}. */
|
|
24659
|
+
variant = input('track', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
24660
|
+
/**
|
|
24661
|
+
* Switch to multi-select: every pressed option is carried in {@link values} and
|
|
24662
|
+
* clicking one toggles it. {@link value} is ignored in this mode.
|
|
24663
|
+
*
|
|
24664
|
+
* A separate model rather than widening `value` to `string | string[]`: a consumer
|
|
24665
|
+
* would then have to narrow on every read, and every EXISTING consumer's
|
|
24666
|
+
* `[(value)]` binding would stop type-checking — a ds-compat MAJOR for a feature
|
|
24667
|
+
* none of them asked for.
|
|
24668
|
+
*/
|
|
24669
|
+
multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : /* istanbul ignore next */ []));
|
|
24670
|
+
/**
|
|
24671
|
+
* The pressed options in {@link multiple} mode. Order is the consumer's; this
|
|
24672
|
+
* component appends on select and filters on deselect, so a caller that treats it
|
|
24673
|
+
* as a set is unaffected and one that treats it as a list keeps its ordering.
|
|
24674
|
+
*/
|
|
24675
|
+
values = model([], ...(ngDevMode ? [{ debugName: "values" }] : /* istanbul ignore next */ []));
|
|
23962
24676
|
selectedIndex = computed(() => this.options().findIndex(o => o.value === this.value()), ...(ngDevMode ? [{ debugName: "selectedIndex" }] : /* istanbul ignore next */ []));
|
|
24677
|
+
/** Selected in whichever model is active. */
|
|
24678
|
+
isOn(value) {
|
|
24679
|
+
return this.multiple() ? this.values().includes(value) : this.value() === value;
|
|
24680
|
+
}
|
|
23963
24681
|
tabIndexFor(index) {
|
|
23964
24682
|
const selected = this.selectedIndex();
|
|
23965
24683
|
return index === (selected >= 0 ? selected : 0) ? 0 : -1;
|
|
23966
24684
|
}
|
|
23967
24685
|
select(value) {
|
|
23968
|
-
if (
|
|
24686
|
+
if (this.disabled())
|
|
24687
|
+
return;
|
|
24688
|
+
if (!this.multiple()) {
|
|
23969
24689
|
this.value.set(value);
|
|
24690
|
+
return;
|
|
24691
|
+
}
|
|
24692
|
+
this.values.update(current => current.includes(value) ? current.filter(v => v !== value) : [...current, value]);
|
|
23970
24693
|
}
|
|
23971
24694
|
onKey(event) {
|
|
23972
|
-
|
|
24695
|
+
// Multi-select is a group of toggle buttons, each its own tab stop: arrows must
|
|
24696
|
+
// stay unbound. Wiring them the way the radiogroup does would move selection as
|
|
24697
|
+
// well as focus, so walking the row with the keyboard would toggle every filter
|
|
24698
|
+
// it passed through.
|
|
24699
|
+
if (this.disabled() || this.multiple())
|
|
23973
24700
|
return;
|
|
23974
24701
|
const next = nextSegmentIndex(this.selectedIndex(), event.key, this.options().length, this.i18n.isRtl());
|
|
23975
24702
|
if (next === null)
|
|
@@ -23979,10 +24706,11 @@ class FlySegmentedComponent {
|
|
|
23979
24706
|
this.el.nativeElement.querySelectorAll('[role="radio"]')[next]?.focus();
|
|
23980
24707
|
}
|
|
23981
24708
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlySegmentedComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
23982
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlySegmentedComponent, isStandalone: true, selector: "fly-segmented", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelKey: { classPropertyName: "ariaLabelKey", publicName: "ariaLabelKey", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, ngImport: i0, template: `
|
|
24709
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FlySegmentedComponent, isStandalone: true, selector: "fly-segmented", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelKey: { classPropertyName: "ariaLabelKey", publicName: "ariaLabelKey", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, values: { classPropertyName: "values", publicName: "values", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", values: "valuesChange" }, ngImport: i0, template: `
|
|
23983
24710
|
<div
|
|
23984
24711
|
class="seg"
|
|
23985
|
-
|
|
24712
|
+
[attr.data-variant]="variant()"
|
|
24713
|
+
[attr.role]="multiple() ? 'group' : 'radiogroup'"
|
|
23986
24714
|
tabindex="-1"
|
|
23987
24715
|
[attr.aria-label]="ariaLabelKey() ? (ariaLabelKey()! | translate) : null"
|
|
23988
24716
|
(keydown)="onKey($event)"
|
|
@@ -23991,10 +24719,11 @@ class FlySegmentedComponent {
|
|
|
23991
24719
|
<button
|
|
23992
24720
|
type="button"
|
|
23993
24721
|
class="seg__btn"
|
|
23994
|
-
role="radio"
|
|
23995
|
-
[class.on]="
|
|
23996
|
-
[attr.aria-checked]="
|
|
23997
|
-
[attr.
|
|
24722
|
+
[attr.role]="multiple() ? null : 'radio'"
|
|
24723
|
+
[class.on]="isOn(opt.value)"
|
|
24724
|
+
[attr.aria-checked]="multiple() ? null : isOn(opt.value)"
|
|
24725
|
+
[attr.aria-pressed]="multiple() ? isOn(opt.value) : null"
|
|
24726
|
+
[attr.tabindex]="multiple() ? null : tabIndexFor($index)"
|
|
23998
24727
|
[disabled]="disabled()"
|
|
23999
24728
|
(click)="select(opt.value)"
|
|
24000
24729
|
>
|
|
@@ -24005,14 +24734,15 @@ class FlySegmentedComponent {
|
|
|
24005
24734
|
</button>
|
|
24006
24735
|
}
|
|
24007
24736
|
</div>
|
|
24008
|
-
`, isInline: true, styles: [":host{display:inline-flex}.seg{display:flex;flex-wrap:wrap;gap:3px;padding:3px;background:var(--w07);border:1px solid var(--w08);border-radius:99px}.seg__btn{display:inline-flex;align-items:center;gap:6px;min-block-size:28px;padding:0 13px;border:0;background:transparent;color:var(--w55);font:500 12.5px/1.3 var(--font-family);font-family:inherit;border-radius:99px;cursor:pointer;white-space:nowrap;transition:background var(--nova-duration-hover) var(--nova-ease-micro),color var(--nova-duration-hover) var(--nova-ease-micro)}.seg__btn:hover{color:var(--w85)}.seg__btn:focus-visible{outline:3px solid var(--accent);outline-offset:3px}.seg__btn:disabled{opacity:.38;filter:saturate(.6);cursor:not-allowed}.seg__btn.on{background:var(--w14);color:var(--w95);font-weight:600;box-shadow:var(--shadow-card),var(--glass2-inset)}.seg__count{font-family:var(--font-mono);font-variant-numeric:tabular-nums;font-size:10.5px;padding:1px 5px;background:var(--w1);border-radius:4px;color:var(--w42)}.seg__btn.on .seg__count{color:var(--w55)}@media(pointer:coarse){.seg__btn{min-block-size:44px}}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
24737
|
+
`, isInline: true, styles: [":host{display:inline-flex}.seg{display:flex;flex-wrap:wrap;gap:3px;padding:3px;background:var(--w07);border:1px solid var(--w08);border-radius:99px}.seg[data-variant=pills]{gap:6px;padding:0;background:transparent;border:0;border-radius:0}.seg__btn{display:inline-flex;align-items:center;gap:6px;min-block-size:28px;padding:0 13px;border:0;background:transparent;color:var(--w55);font:500 12.5px/1.3 var(--font-family);font-family:inherit;border-radius:99px;cursor:pointer;white-space:nowrap;transition:background var(--nova-duration-hover) var(--nova-ease-micro),color var(--nova-duration-hover) var(--nova-ease-micro)}.seg__btn:hover{color:var(--w85)}.seg__btn:focus-visible{outline:3px solid var(--accent);outline-offset:3px}.seg__btn:disabled{opacity:.38;filter:saturate(.6);cursor:not-allowed}.seg__btn.on{background:var(--w14);color:var(--w95);font-weight:600;box-shadow:var(--shadow-card),var(--glass2-inset)}.seg__count{font-family:var(--font-mono);font-variant-numeric:tabular-nums;font-size:10.5px;padding:1px 5px;background:var(--w1);border-radius:4px;color:var(--w42)}.seg__btn.on .seg__count{color:var(--w55)}.seg[data-variant=pills] .seg__btn{min-block-size:30px;padding:0 12px;border:.5px solid var(--w12);background:var(--w04);color:var(--w7);border-radius:99px;transition:background var(--nova-duration-hover) var(--nova-ease-micro),color var(--nova-duration-hover) var(--nova-ease-micro),border-color var(--nova-duration-hover) var(--nova-ease-micro)}.seg[data-variant=pills] .seg__btn:hover:not(.on){background:var(--w08);border-color:var(--w2);color:var(--ink)}.seg[data-variant=pills] .seg__btn.on{border-color:transparent;background:var(--accent);color:var(--on-accent);font-weight:500;box-shadow:none}.seg[data-variant=pills] .seg__count{padding:2px 6px;border-radius:7px;font-family:var(--font-family);font-size:10.5px;font-weight:var(--fw-semibold);color:inherit}.seg[data-variant=pills] .seg__btn.on .seg__count{background:var(--w22);color:inherit}@media(pointer:coarse){.seg__btn,.seg[data-variant=pills] .seg__btn{min-block-size:44px}}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
24009
24738
|
}
|
|
24010
24739
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FlySegmentedComponent, decorators: [{
|
|
24011
24740
|
type: Component,
|
|
24012
24741
|
args: [{ selector: 'fly-segmented', standalone: true, imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
24013
24742
|
<div
|
|
24014
24743
|
class="seg"
|
|
24015
|
-
|
|
24744
|
+
[attr.data-variant]="variant()"
|
|
24745
|
+
[attr.role]="multiple() ? 'group' : 'radiogroup'"
|
|
24016
24746
|
tabindex="-1"
|
|
24017
24747
|
[attr.aria-label]="ariaLabelKey() ? (ariaLabelKey()! | translate) : null"
|
|
24018
24748
|
(keydown)="onKey($event)"
|
|
@@ -24021,10 +24751,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
|
|
|
24021
24751
|
<button
|
|
24022
24752
|
type="button"
|
|
24023
24753
|
class="seg__btn"
|
|
24024
|
-
role="radio"
|
|
24025
|
-
[class.on]="
|
|
24026
|
-
[attr.aria-checked]="
|
|
24027
|
-
[attr.
|
|
24754
|
+
[attr.role]="multiple() ? null : 'radio'"
|
|
24755
|
+
[class.on]="isOn(opt.value)"
|
|
24756
|
+
[attr.aria-checked]="multiple() ? null : isOn(opt.value)"
|
|
24757
|
+
[attr.aria-pressed]="multiple() ? isOn(opt.value) : null"
|
|
24758
|
+
[attr.tabindex]="multiple() ? null : tabIndexFor($index)"
|
|
24028
24759
|
[disabled]="disabled()"
|
|
24029
24760
|
(click)="select(opt.value)"
|
|
24030
24761
|
>
|
|
@@ -24035,8 +24766,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
|
|
|
24035
24766
|
</button>
|
|
24036
24767
|
}
|
|
24037
24768
|
</div>
|
|
24038
|
-
`, styles: [":host{display:inline-flex}.seg{display:flex;flex-wrap:wrap;gap:3px;padding:3px;background:var(--w07);border:1px solid var(--w08);border-radius:99px}.seg__btn{display:inline-flex;align-items:center;gap:6px;min-block-size:28px;padding:0 13px;border:0;background:transparent;color:var(--w55);font:500 12.5px/1.3 var(--font-family);font-family:inherit;border-radius:99px;cursor:pointer;white-space:nowrap;transition:background var(--nova-duration-hover) var(--nova-ease-micro),color var(--nova-duration-hover) var(--nova-ease-micro)}.seg__btn:hover{color:var(--w85)}.seg__btn:focus-visible{outline:3px solid var(--accent);outline-offset:3px}.seg__btn:disabled{opacity:.38;filter:saturate(.6);cursor:not-allowed}.seg__btn.on{background:var(--w14);color:var(--w95);font-weight:600;box-shadow:var(--shadow-card),var(--glass2-inset)}.seg__count{font-family:var(--font-mono);font-variant-numeric:tabular-nums;font-size:10.5px;padding:1px 5px;background:var(--w1);border-radius:4px;color:var(--w42)}.seg__btn.on .seg__count{color:var(--w55)}@media(pointer:coarse){.seg__btn{min-block-size:44px}}\n"] }]
|
|
24039
|
-
}], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], ariaLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelKey", required: false }] }] } });
|
|
24769
|
+
`, styles: [":host{display:inline-flex}.seg{display:flex;flex-wrap:wrap;gap:3px;padding:3px;background:var(--w07);border:1px solid var(--w08);border-radius:99px}.seg[data-variant=pills]{gap:6px;padding:0;background:transparent;border:0;border-radius:0}.seg__btn{display:inline-flex;align-items:center;gap:6px;min-block-size:28px;padding:0 13px;border:0;background:transparent;color:var(--w55);font:500 12.5px/1.3 var(--font-family);font-family:inherit;border-radius:99px;cursor:pointer;white-space:nowrap;transition:background var(--nova-duration-hover) var(--nova-ease-micro),color var(--nova-duration-hover) var(--nova-ease-micro)}.seg__btn:hover{color:var(--w85)}.seg__btn:focus-visible{outline:3px solid var(--accent);outline-offset:3px}.seg__btn:disabled{opacity:.38;filter:saturate(.6);cursor:not-allowed}.seg__btn.on{background:var(--w14);color:var(--w95);font-weight:600;box-shadow:var(--shadow-card),var(--glass2-inset)}.seg__count{font-family:var(--font-mono);font-variant-numeric:tabular-nums;font-size:10.5px;padding:1px 5px;background:var(--w1);border-radius:4px;color:var(--w42)}.seg__btn.on .seg__count{color:var(--w55)}.seg[data-variant=pills] .seg__btn{min-block-size:30px;padding:0 12px;border:.5px solid var(--w12);background:var(--w04);color:var(--w7);border-radius:99px;transition:background var(--nova-duration-hover) var(--nova-ease-micro),color var(--nova-duration-hover) var(--nova-ease-micro),border-color var(--nova-duration-hover) var(--nova-ease-micro)}.seg[data-variant=pills] .seg__btn:hover:not(.on){background:var(--w08);border-color:var(--w2);color:var(--ink)}.seg[data-variant=pills] .seg__btn.on{border-color:transparent;background:var(--accent);color:var(--on-accent);font-weight:500;box-shadow:none}.seg[data-variant=pills] .seg__count{padding:2px 6px;border-radius:7px;font-family:var(--font-family);font-size:10.5px;font-weight:var(--fw-semibold);color:inherit}.seg[data-variant=pills] .seg__btn.on .seg__count{background:var(--w22);color:inherit}@media(pointer:coarse){.seg__btn,.seg[data-variant=pills] .seg__btn{min-block-size:44px}}\n"] }]
|
|
24770
|
+
}], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], ariaLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelKey", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], values: [{ type: i0.Input, args: [{ isSignal: true, alias: "values", required: false }] }, { type: i0.Output, args: ["valuesChange"] }] } });
|
|
24040
24771
|
|
|
24041
24772
|
/**
|
|
24042
24773
|
* Switch toggle (`role="switch"`). No legacy visual source existed — minimal
|
|
@@ -25793,5 +26524,5 @@ const AUDIENCE_ERROR_CODES = {
|
|
|
25793
26524
|
* Generated bundle index. Do not edit.
|
|
25794
26525
|
*/
|
|
25795
26526
|
|
|
25796
|
-
export { AGENT_DRAG_MIME, AGENT_PAYLOAD_VERSION, APP_LOOKUP, AUDIENCE_ERROR_CODES, AUDIENCE_LIMITS, AUDIENCE_PRESETS, AUDIENCE_TERM_KINDS, AgentActionBus, AgentActionUnsupportedDispatchError, AgentCommandRegistry, AgentDropRegistry, AgentFlightAnimator, AgentLookupRegistry, AgentPayloadOversizeError, AudienceBuilderComponent, AuthService, BilingualFieldComponent, CRON_MODES, CRON_WEEKDAYS, ContextMenuComponent, DEFAULT_AGENT_PAYLOAD_LIMITS, DEFAULT_FLY_THEME_MODE, DS_BASELINE_LOCALES, DashboardKpiComponent, DialogResult, ENTITY_LINK_LAUNCHER, EntityLookupComponent, FLYOS_LAUNCH_EVENT, FLYOS_LAUNCH_REQUEST_EVENT, FLYOS_REMOTE_ROUTE_EVENT, FLY_ACCENT_PROPERTY, FLY_ADMIN_ROLES, FLY_CHUNK_RELOAD_FLAG, FLY_COMPACT_THRESHOLD, FLY_COUNTRIES, FLY_CURRENCIES_BRIEF_ENDPOINT, FLY_EMOJI_BY_ID, FLY_EMOJI_CATEGORIES, FLY_EMOJI_CATEGORY_BY_ID, FLY_EMOJI_DEFAULT_CATEGORIES, FLY_EMOJI_PACK, FLY_EMOJI_PACK_MISSING_MESSAGE, FLY_EMOJI_PREVIEW_COUNT, FLY_EMPTY_VALUE, FLY_LOCALE_CATALOG, FLY_MAGIC_BAR_EVENT, FLY_MAGIC_BAR_ICONS, FLY_MAGIC_BAR_STORE_KEY, FLY_NF_CACHE_HEAL_FLAG, FLY_RELOAD_DEBOUNCE_MS, FLY_RELOAD_REARM_MS, FLY_REMOTE_BASE_PATH, FLY_REMOTE_CONTEXT_EVENT, FLY_REMOTE_CONTEXT_STORE_KEY, FLY_REMOTE_ROUTES, FLY_SCAN_PENDING_DEFAULT_DELAY_MS, FLY_SCAN_PENDING_MAX_RETRIES, FLY_SCAN_PENDING_STATUS, FLY_SEARCH_DEBOUNCE_MS, FLY_SKIN_TONES, FLY_STANDALONE_AUTH_CONFIG, FLY_THEME_MODE_IDS, FLY_VIEWPORT_IS_MOBILE, FLY_WINDOW_HELP_HINT_EVENT, FLY_WINDOW_PAGE_TITLE_EVENT, FlyAchievementProgressComponent, FlyActionMenuComponent, FlyActionStackComponent, FlyAgentDraggableDirective, FlyAnimatedEmojiComponent, FlyAppHomeComponent, FlyAppTopbarComponent, FlyAppUnavailableComponent, FlyAuthLiveRefresh, FlyBadgeWallComponent, FlyBlockUiComponent, FlyBreadcrumbComponent, FlyButtonComponent, FlyBytesPipe, FlyCaptchaComponent, FlyCardActionsComponent, FlyCardBodyComponent, FlyCardComponent, FlyCardFooterComponent, FlyCardGridComponent, FlyCardMediaComponent, FlyCardMetaComponent, FlyCardStatusComponent, FlyCardTitleComponent, FlyCellDirective, FlyCheckboxComponent, FlyChipComponent, FlyChunkReloadErrorHandler, FlyClickOutsideDirective, FlyCollapsibleComponent, FlyCommentThreadComponent, FlyCompactNumberPipe, FlyConfirmDialogComponent, FlyControlDirective, FlyCronBuilderComponent, FlyCurrencyCatalogService, FlyCurrencySelectorComponent, FlyDataTableComponent, FlyDatePipe, FlyDateTimePipe, FlyDebouncer, FlyDecimalNumberPipe, FlyDeepLinkPrefetchService, FlyDetailCardComponent, FlyDetailShellComponent, FlyDrawerComponent, FlyDurationPipe, FlyDynamicFormComponent, FlyEmojiPickerComponent, FlyFieldComponent, FlyFileDownloadService, FlyFileUploadComponent, FlyFilterPanelComponent, FlyFormBannerComponent, FlyFormFooterComponent, FlyFormGridComponent, FlyFormSectionComponent, FlyFullNumberPipe, FlyGanttComponent, FlyHubClient, FlyIconButtonComponent, FlyIfAdminDirective, FlyIfRoleDirective, FlyImageUploadComponent, FlyLeaderboardComponent, FlyMagicActionsComponent, FlyMetaListComponent, FlyModalComponent, FlyModerationQueueComponent, FlyModuleIconDirective, FlyMoneyPipe, FlyPaginationComponent, FlyPeoplePickerComponent, FlyPointsTierComponent, FlyProgressComponent, FlyRelativeTimePipe, FlyRemoteContextService, FlyRemoteRouter, FlyRemoteRouterOutletComponent, FlySearchInputComponent, FlySectionHeaderComponent, FlySecureSrcDirective, FlySegmentedComponent, FlySelectComponent, FlySkeletonComponent, FlySliderComponent, FlySparklineComponent, FlySpinnerComponent, FlyStandaloneAuthCallbackComponent, FlyStandaloneAuthService, FlyStandaloneMagicActionsComponent, FlyStateMessageComponent, FlyDynamicFormComponent as FlySurveyFormComponent, FlyTabComponent, FlyTabsComponent, FlyTagsInputComponent, FlyThemeService, FlyTimePipe, FlyToastService, FlyToggleComponent, FlyTooltipDirective, FlyTreeNavComponent, FlyTypeaheadComponent, FlyUserDirectoryService, FlyWindowHelpService, FlyWindowTitleService, FlyosPendingLaunchesGlobalKey, FlyosShellHandlesLaunchRequestsGlobalKey, FlyosShellOwnsHistoryGlobalKey, GANTT_DEPENDENCY_TYPES, GANTT_ROW_TINT_ALPHA, GANTT_ZOOMS, I18nService, LAUNCH_CONTEXT, MAGIC_BAR_SEARCH_WIDTH_DEFAULT, MAGIC_BAR_SEARCH_WIDTH_MAX, MAGIC_BAR_SEARCH_WIDTH_MIN, MagicBarRegistry, MessageBoxButtons, MessageBoxComponent, MessageBoxIcon, MessageBoxService, MockAuthService, NOVA_PRIORITY_TONE, NOVA_STATUS_TONE, NOVA_TONE_FALLBACK, OverlayStack, PRESENCE_COLORS, PageHeaderComponent, RTL_LOCALE_SET, SHARE_ORG_CHART_SYSTEM_KEY_APPS, SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT, SHARE_PANEL_DEFAULT_FILE_LEVELS, STATE_DEFAULT_ICONS, STATE_DEFAULT_MESSAGE_KEYS, STEP_UP_CODE, STEP_UP_REAUTH_HANDLER, STEP_UP_RETURN_KEY, SUPPORTED_AGENT_PAYLOAD_VERSIONS, SharePanelComponent, SourceAppResolver, StandaloneWindowManagerService, StatusBadgeComponent, StepUpService, ToastHostComponent, TranslatePipe, WINDOW_DATA, WINDOW_HELP_HINT, WindowManagerService, applySkinTone, applySuggestion, ariaSort, buildCron, canConfirm, canGoNext, canGoPrev, captureFocus, clampMagicBarSearchWidth, clampPage, clampSliderValue, connectRemoteLaunch, enterActivatesNatively, filterGroup, filterSuggestions, findLocaleByDialect, findLocaleByPrefix, firstEnabledIndex, firstModuleIndex, flattenModules, flyAdminGuard, flyApiErrorMessage, flyCaptchaBase64Utf8, flyCaptchaBuildToken, flyCaptchaSha256Hex, flyCompactNumber, flyDebounced, flyDecimalNumber, flyDownloadBlob, flyDuration, flyEmojiFor, flyEmojiIsTonable, flyEmojiPackUrl, flyExportFileName, flyFirstEnabledIndex, flyFormatBytes, flyFormatDate, flyFormatDateTime, flyFormatMoney, flyFormatTime, flyFullNumber, flyNextEnabledIndex, flyNextSegmentIndex, flyRelativeTime, flyResolveActiveTab, flyRoleGuard, flyScanPendingDelayMs, flyScanRetry, flySignedCompact, flySkinToneLabelKey, flyStandaloneAuthGuard, flyStandaloneAuthInterceptor, flyToDateOnly, flyToPage, flyUnwrap, flyUnwrapCurrencies, flyUnwrapLenient, hasAnyRoleIn, healNativeFederationCacheOnce, initialExpanded, isAccentColor, isChunkLoadError, isFlyAuthStorePopulated, isNativeFederationCacheError, isRtlLocale, isRtlLocaleEntry, isValidCron, isValidSingleField, loadRemoteStyles, magicBarOwnerKey, magicBarSearchText, matchFlyRoutePattern, nextEnabledIndex, nextModuleIndex, nextSegmentIndex, nextSort, normalizeFlyTheme, normalizeRoles, novaPriorityToneVar, novaStatusToneVar, overlayStack, parseCron, parseStepUpChallenge, parseTags, prefetchRemoteStyles, presenceColorFor, printConsoleSecurityWarning, provideFlyChunkReloadRecovery, provideFlyEmojiPack, provideFlyStandaloneAuth, reloadOnceForChunkError, requestAppLaunch, resolveActiveModule, resolveActiveSection, resolveActiveTab, resolveCellValue, resolveStateIcon, resolveStateMessageKey, restoreFocus, sameTags, sectionHintKey, sectionLayout, sliderTrackGradient, sparklinePath, sparklinePoints, stepUpInterceptor, stepUpReturnKey, trimAgentPayload, trimAgentString, unloadRemoteStyles, utf8ByteLength, validateAgentPayload, warnIfEmbeddedSessionMissing };
|
|
26527
|
+
export { AGENT_DRAG_MIME, AGENT_PAYLOAD_VERSION, APP_LOOKUP, AUDIENCE_ERROR_CODES, AUDIENCE_LIMITS, AUDIENCE_PRESETS, AUDIENCE_TERM_KINDS, AgentActionBus, AgentActionUnsupportedDispatchError, AgentCommandRegistry, AgentDropRegistry, AgentFlightAnimator, AgentLookupRegistry, AgentPayloadOversizeError, AudienceBuilderComponent, AuthService, BilingualFieldComponent, CRON_MODES, CRON_WEEKDAYS, ContextMenuComponent, DEFAULT_AGENT_PAYLOAD_LIMITS, DEFAULT_FLY_THEME_MODE, DS_BASELINE_LOCALES, DashboardKpiComponent, DialogResult, ENTITY_LINK_LAUNCHER, EntityLookupComponent, FLYOS_LAUNCH_EVENT, FLYOS_LAUNCH_REQUEST_EVENT, FLYOS_REMOTE_ROUTE_EVENT, FLY_ACCENT_PROPERTY, FLY_ADMIN_ROLES, FLY_CHUNK_RELOAD_FLAG, FLY_COMPACT_THRESHOLD, FLY_COUNTRIES, FLY_CURRENCIES_BRIEF_ENDPOINT, FLY_CURRENCY_DEFAULT_ENDPOINT, FLY_EMOJI_BY_ID, FLY_EMOJI_CATEGORIES, FLY_EMOJI_CATEGORY_BY_ID, FLY_EMOJI_DEFAULT_CATEGORIES, FLY_EMOJI_PACK, FLY_EMOJI_PACK_MISSING_MESSAGE, FLY_EMOJI_PREVIEW_COUNT, FLY_EMPTY_VALUE, FLY_LOCALE_CATALOG, FLY_MAGIC_BAR_EVENT, FLY_MAGIC_BAR_ICONS, FLY_MAGIC_BAR_STORE_KEY, FLY_NF_CACHE_HEAL_FLAG, FLY_RELOAD_DEBOUNCE_MS, FLY_RELOAD_REARM_MS, FLY_REMOTE_BASE_PATH, FLY_REMOTE_CONTEXT_EVENT, FLY_REMOTE_CONTEXT_STORE_KEY, FLY_REMOTE_ROUTES, FLY_SCAN_PENDING_DEFAULT_DELAY_MS, FLY_SCAN_PENDING_MAX_RETRIES, FLY_SCAN_PENDING_STATUS, FLY_SEARCH_DEBOUNCE_MS, FLY_SKIN_TONES, FLY_STANDALONE_AUTH_CONFIG, FLY_THEME_MODE_IDS, FLY_VIEWPORT_IS_MOBILE, FLY_WINDOW_BREADCRUMBS_EVENT, FLY_WINDOW_BREADCRUMBS_STORE_KEY, FLY_WINDOW_HELP_HINT_EVENT, FLY_WINDOW_PAGE_TITLE_EVENT, FlyAchievementProgressComponent, FlyActionMenuComponent, FlyActionStackComponent, FlyAgentDraggableDirective, FlyAnimatedEmojiComponent, FlyAppHomeComponent, FlyAppTopbarComponent, FlyAppUnavailableComponent, FlyAuthLiveRefresh, FlyBadgeWallComponent, FlyBlockUiComponent, FlyBreadcrumbComponent, FlyButtonComponent, FlyBytesPipe, FlyCaptchaComponent, FlyCardActionsComponent, FlyCardBodyComponent, FlyCardComponent, FlyCardFooterComponent, FlyCardGridComponent, FlyCardMediaComponent, FlyCardMetaComponent, FlyCardStatusComponent, FlyCardTitleComponent, FlyCellDirective, FlyCheckboxComponent, FlyChipComponent, FlyChunkReloadErrorHandler, FlyClickOutsideDirective, FlyCollapsibleComponent, FlyCommentThreadComponent, FlyCompactNumberPipe, FlyConfirmDialogComponent, FlyControlDirective, FlyCronBuilderComponent, FlyCurrencyCatalogService, FlyCurrencySelectorComponent, FlyDataTableComponent, FlyDatePipe, FlyDateTimePipe, FlyDebouncer, FlyDecimalNumberPipe, FlyDeepLinkPrefetchService, FlyDetailCardComponent, FlyDetailShellComponent, FlyDrawerComponent, FlyDurationPipe, FlyDynamicFormComponent, FlyEmojiPickerComponent, FlyFieldComponent, FlyFileDownloadService, FlyFileUploadComponent, FlyFilterPanelComponent, FlyFormBannerComponent, FlyFormFooterComponent, FlyFormGridComponent, FlyFormSectionComponent, FlyFullNumberPipe, FlyGanttComponent, FlyHubClient, FlyIconButtonComponent, FlyIfAdminDirective, FlyIfRoleDirective, FlyImageUploadComponent, FlyLeaderboardComponent, FlyMagicActionsComponent, FlyMetaListComponent, FlyModalComponent, FlyModerationQueueComponent, FlyModuleIconDirective, FlyMoneyPipe, FlyPagerComponent, FlyPaginationComponent, FlyPeoplePickerComponent, FlyPointsTierComponent, FlyProgressComponent, FlyRelativeTimePipe, FlyRemoteContextService, FlyRemoteRouter, FlyRemoteRouterOutletComponent, FlySearchInputComponent, FlySectionHeaderComponent, FlySecureSrcDirective, FlySegmentedComponent, FlySelectComponent, FlySkeletonComponent, FlySliderComponent, FlySparklineComponent, FlySpinnerComponent, FlyStandaloneAuthCallbackComponent, FlyStandaloneAuthService, FlyStandaloneMagicActionsComponent, FlyStateMessageComponent, FlyDynamicFormComponent as FlySurveyFormComponent, FlyTabComponent, FlyTabsComponent, FlyTagsInputComponent, FlyThemeService, FlyTimePipe, FlyToastService, FlyToggleComponent, FlyTooltipDirective, FlyTreeNavComponent, FlyTypeaheadComponent, FlyUserDirectoryService, FlyWindowHelpService, FlyWindowTitleService, FlyosPendingLaunchesGlobalKey, FlyosShellHandlesLaunchRequestsGlobalKey, FlyosShellOwnsHistoryGlobalKey, GANTT_DEPENDENCY_TYPES, GANTT_ROW_TINT_ALPHA, GANTT_ZOOMS, I18nService, LAUNCH_CONTEXT, MAGIC_BAR_SEARCH_WIDTH_DEFAULT, MAGIC_BAR_SEARCH_WIDTH_MAX, MAGIC_BAR_SEARCH_WIDTH_MIN, MagicBarRegistry, MessageBoxButtons, MessageBoxComponent, MessageBoxIcon, MessageBoxService, MockAuthService, NOVA_PRIORITY_TONE, NOVA_STATUS_TONE, NOVA_TONE_FALLBACK, OverlayStack, PRESENCE_COLORS, PageHeaderComponent, RTL_LOCALE_SET, SHARE_ORG_CHART_SYSTEM_KEY_APPS, SHARE_ORG_CHART_SYSTEM_KEY_DEFAULT, SHARE_PANEL_DEFAULT_FILE_LEVELS, STATE_DEFAULT_ICONS, STATE_DEFAULT_MESSAGE_KEYS, STEP_UP_CODE, STEP_UP_REAUTH_HANDLER, STEP_UP_RETURN_KEY, SUPPORTED_AGENT_PAYLOAD_VERSIONS, SharePanelComponent, SourceAppResolver, StandaloneWindowManagerService, StatusBadgeComponent, StepUpService, ToastHostComponent, TranslatePipe, WINDOW_DATA, WINDOW_HELP_HINT, WindowBreadcrumbsRegistry, WindowManagerService, applySkinTone, applySuggestion, ariaSort, buildCron, canConfirm, canGoNext$1 as canGoNext, canGoPrev$1 as canGoPrev, captureFocus, clampMagicBarSearchWidth, clampPage$1 as clampPage, clampSliderValue, connectRemoteLaunch, enterActivatesNatively, filterGroup, filterSuggestions, findLocaleByDialect, findLocaleByPrefix, firstEnabledIndex, firstModuleIndex, flattenModules, flyAdminGuard, flyApiErrorMessage, flyCaptchaBase64Utf8, flyCaptchaBuildToken, flyCaptchaSha256Hex, flyCompactNumber, flyDebounced, flyDecimalNumber, flyDownloadBlob, flyDuration, flyEmojiFor, flyEmojiIsTonable, flyEmojiPackUrl, flyExportFileName, flyFirstEnabledIndex, flyFormatBytes, flyFormatDate, flyFormatDateTime, flyFormatMoney, flyFormatTime, flyFullNumber, flyNextEnabledIndex, flyNextSegmentIndex, flyRelativeTime, flyResolveActiveTab, flyRoleGuard, flyScanPendingDelayMs, flyScanRetry, flySignedCompact, flySkinToneLabelKey, flyStandaloneAuthGuard, flyStandaloneAuthInterceptor, flyToDateOnly, flyToPage, flyUnwrap, flyUnwrapCurrencies, flyUnwrapCurrency, flyUnwrapLenient, hasAnyRoleIn, healNativeFederationCacheOnce, initialExpanded, isAccentColor, isChunkLoadError, isFlyAuthStorePopulated, isNativeFederationCacheError, isRtlLocale, isRtlLocaleEntry, isValidCron, isValidSingleField, loadRemoteStyles, magicBarOwnerKey, magicBarSearchText, matchFlyRoutePattern, nextEnabledIndex, nextModuleIndex, nextSegmentIndex, nextSort, normalizeFlyTheme, normalizeRoles, novaPriorityToneVar, novaStatusToneVar, overlayStack, canGoNext as pagerCanGoNext, canGoPrev as pagerCanGoPrev, clampPage as pagerClampPage, parseCron, parseStepUpChallenge, parseTags, prefetchRemoteStyles, presenceColorFor, printConsoleSecurityWarning, provideFlyChunkReloadRecovery, provideFlyEmojiPack, provideFlyStandaloneAuth, reloadOnceForChunkError, requestAppLaunch, resolveActiveModule, resolveActiveSection, resolveActiveTab, resolveCellValue, resolveStateIcon, resolveStateMessageKey, restoreFocus, sameTags, sectionHintKey, sectionLayout, sliderTrackGradient, sparklinePath, sparklinePoints, stepUpInterceptor, stepUpReturnKey, trimAgentPayload, trimAgentString, unloadRemoteStyles, utf8ByteLength, validateAgentPayload, warnIfEmbeddedSessionMissing };
|
|
25797
26528
|
//# sourceMappingURL=flyos-design-system.mjs.map
|